Working with Models
Up to this point, every cell on the graph has been a record: a JSON-like description you write into initialCells and read back with useCell. Behind that JSON, JointJS keeps a live dia.Cell instance, an object with its own attributes and room for methods you add yourself. That object is the model. It is the place to read cell state imperatively in callbacks and to package a cell type's structure (and, if you need it, behavior) through subclassing.
Where you get a model​
Several joint-react surfaces hand you the model directly:
useOnPaperEvents: cell handlers receivemodel(the cell) in their params.<Paper>callback props that carry a model on their context:model:connectionStrategy,cellVisibility,interactive.child.model/parent.model:validateEmbedding.child.model:validateUnembedding(noparent, since the cell is leaving its current parent rather than entering a new one).source.model:defaultLink(a link drag has no target yet, so the factory receives only the source end).source.model/target.model:validateConnection(fires once both ends are known).
In every one of these model is the same object: a live JointJS cell with methods, attributes, and events.
Accessing model attributes​
The same data: { … } you put on records lives on the model as the data attribute. Anywhere joint-react hands you a model, get('data') reads it, typically to make a decision the callback needs:
<Paper
validateConnection={({ source, target }) =>
source.model.get('data')?.kind === 'output' &&
target.model.get('data')?.kind === 'input'
}
/>
For writes from React code, reach for setCell, which keeps mutations on the React-controlled path. model.set('data', …) is for code that already runs outside the React loop.
get and set are not limited to data. They work for any model attribute, including position, size, z, attrs, and anything you add in defaults(). See get() and set() on mvc.Model, plus the dia.Cell, dia.Element, and dia.Link pages for cell-, element-, and link-specific helpers.
Custom models​
Subclass ElementModel (or LinkModel) when you want a cell type to carry its own structure (default data, attrs, ports, size, anything that's the same across instances) instead of repeating those fields on every record. Override defaults() and merge super.defaults(); every record with that type inherits the baked-in shape.
Three things to wire up:
- Subclass
ElementModel. Overridedefaults()and mergesuper.defaults(). - Pass the class to
<GraphProvider cellNamespace={{ StickyNote }}>so JointJS rehydrates records that carrytype: 'StickyNote'into your subclass instances. (See Using Built-in Shapes for the same pattern applied to native JointJS shapes.) - Set
type: 'StickyNote'on the matching records.
import { ElementModel } from '@joint/react';
export type StickyColor = 'amber' | 'sky';
export interface StickyData {
text: string;
color: StickyColor;
}
export class StickyNote extends ElementModel<{ data: StickyData }> {
defaults() {
return {
...super.defaults(),
type: 'StickyNote',
data: { text: 'New note', color: 'amber' as StickyColor },
};
}
}
Records can drop everything the class already provides. A record that omits data entirely picks up the model's defaults: text: 'New note', color: 'amber'. A record that supplies data overrides it:
// uses defaults
{ id: 'a', type: 'StickyNote', position: { x: 30, y: 30 } }
// overrides
{
id: 'b',
type: 'StickyNote',
position: { x: 220, y: 30 },
data: { text: 'Buy milk', color: 'sky' }
}
The first card carries no data on its record, so it inherits the model's defaults. The other one overrides text and color. The class owns the default text and color; records only carry what differs. The same comparison runs on the export side too. See JSON Export for how exportToJSON() strips attributes that match the model's defaults.
Subclasses can carry methods too. They earn their keep when behavior genuinely needs the model: graph traversal via this.graph, geometry helpers using this.position() and this.size(), or native JointJS APIs like embed. For straightforward data updates, prefer setCell; it stays on the React-controlled path.