# v2.0.5 → v2.0.6 migration

This is a patch release that contains **breaking changes** (recorded after the fact). Audit your usage before bumping.

Grep your consumer app for affected imports:

```
rg -n "useThreeObject|useCallbackOnce" --type ts --type tsx
```

---

## `@yaoc1996/js-tools/react-three` — `useThreeObject`

### What changed

1. **Overload removed.** The first argument must now be a `CreateObjectEffect<Mode>` function. Passing an `Object3D` directly (or `undefined`) no longer compiles.
2. **`deps` argument added.** The effect now re-runs only when `deps` change. Previously it re-ran whenever `createObject` identity changed.
3. **`control.forceUpdate()` calls removed.** The hook no longer forces a re-render after add/remove.

### Before

```ts
// Direct Object3D
useThreeObject(myMesh);

// Conditional Object3D
useThreeObject(condition ? myMesh : undefined);

// Function form (identity-driven re-runs)
useThreeObject(({ scene }) => {
    const mesh = makeMesh();
    return [mesh, () => mesh.geometry.dispose()];
});
```

### After

```ts
// Wrap the object in a function
useThreeObject(() => myMesh, []);

// Conditional — return undefined from the function
useThreeObject(() => (condition ? myMesh : undefined), [condition]);

// Function form with explicit deps
useThreeObject(
    ({ scene }) => {
        const mesh = makeMesh();
        return [mesh, () => mesh.geometry.dispose()];
    },
    [/* deps that should trigger re-creation */]
);
```

### If you relied on `control.forceUpdate()`

The hook no longer triggers a re-render of the parent on mount/unmount. If your UI depended on that side effect, trigger it yourself — e.g., bump a state value from the same effect, or use `useThreeView()` and call `control.forceUpdate()` explicitly.

---

## `@yaoc1996/js-tools/react-utils` — `useCallbackOnce`

### What changed

`deps` argument added. The once-gate now resets only when `deps` change. Previously it reset whenever the `effect` identity changed.

### Migration

If you want the **old behavior** (reset on identity change), pass the effect's deps explicitly:

```ts
// Before — implicit identity-driven reset
const handler = useCallbackOnce(onClick);

// After — preserve the old behavior
const handler = useCallbackOnce(onClick, [onClick]);
```

If you want the **new default** (run at most once across the lifetime regardless of identity churn), no change needed:

```ts
const handler = useCallbackOnce(onClick); // never re-arms
```

### Type-level note

The new signature is backward-compatible at the type level (`deps` defaults to `[]`), so call sites still compile. The behavior change is silent — type-check alone will not surface it. Read each call site and decide whether it relied on the old reset semantics.
