Add a reuse prop for named material instances
Summary
Add a reuse prop to material elements. When a reusable material has a non-empty name, R3F should register it in a root-scoped material store. Later material elements with the same name and reuse should resolve to the existing material instance instead of constructing another one.
Target release: 10.1.
Proposed API
<mesh>
<boxGeometry />
<meshStandardNodeMaterial
reuse
name="sharedChrome"
color="#ffffff"
metalness={1}
roughness={0.15}
/>
</mesh>
<mesh position-x={2}>
<sphereGeometry />
<meshStandardNodeMaterial reuse name="sharedChrome" />
</mesh>Both meshes should reference the same MeshStandardNodeMaterial object.
A material with reuse but no usable name should warn or throw in development because there is no deterministic lookup key.
Motivation
Sharing materials currently requires manually hoisting an instance through useMemo, a loader result, context, or another store and then passing it through each mesh's material prop:
const material = useMemo(
() => new THREE.MeshStandardNodeMaterial({ color: '#ffffff' }),
[],
)
return (
<>
<mesh material={material} />
<mesh material={material} />
</>
)That works, but it moves material ownership out of the declarative scene graph. A named reuse path would keep the material definition in JSX while avoiding duplicate material objects, shader setup, and manual plumbing.
This would follow the create-if-not-exists model used by v10's node/resource stores: the name acts as the stable key, the first encounter creates/registers the resource, and later encounters recall it from the same root store.
Suggested semantics
- The registry is scoped to the R3F root/Canvas; names do not leak across roots.
- Only material elements opt in. Existing unnamed/non-
reusematerial behavior remains unchanged. - The first
reuse + nameencounter creates and registers the material. - Later compatible encounters recall the exact same material identity.
- Different material constructors under the same reusable name should produce a clear development error.
- Unmounting one consumer must not dispose a material still referenced by another consumer.
- The material should be disposed when its final owner/reference leaves the root, unless disposal is explicitly disabled.
- HMR and StrictMode remounts should preserve the same safety guarantees as the v10 resource registry.
Prop ownership needs an explicit rule
Multiple JSX elements cannot independently own conflicting props on one mutable material without last-writer ambiguity. A 10.1 implementation should choose and document one policy. For example:
- Definition + references: the first declaration owns material props; later
reusedeclarations are references and may only providename,reuse, and attachment props. - Shared live updates: later declarations apply props to the shared object, with development warnings when more than one mounted declaration supplies material props.
The first policy is more deterministic and is my preference, but either is better than implicit render-order behavior.
Reconciler constraint
A naive implementation cannot simply mount the same object as multiple ordinary host instances. R3F currently stores reconciler metadata on object.__r3f, including a single parent/attachment relationship. A reused material may be attached to many meshes, so the material resource identity and each JSX attachment/consumer need separate lifecycle bookkeeping or reference counting.
The desired result is shared Three.js material identity without corrupting parent metadata, detaching another consumer, or disposing the shared object too early.
Possible store shape
Conceptually:
materials[name] = {
material,
constructor,
references,
owner,
}This does not need to be public API initially, but exposing a read hook later could make the feature compose with imperative code in the same way as the v10 node stores.
Acceptance criteria
- Two compatible
reusematerial elements with the same name attach the same material object to different meshes. - Different reusable names create different materials.
- Reuse is isolated between Canvas roots.
- StrictMode does not duplicate or prematurely dispose the material.
- Removing one mesh leaves the other mesh's material valid.
- Removing the final reference disposes the material exactly once.
- Constructor and conflicting-prop behavior is covered by development warnings/errors and tests.
Source: pmndrs/react-three-fiber