I am trying to create a recursive component that stores it's state in a mobx observable object.
For some peculiar reason, if the observable links another observable in it's properties, the observer
hoc from mobx-react-lite does not trigger re-render, when the observable state changes.
Here is the example, where the elements, that are more than 1 deep, are not re-rendered, when the observable state changes.
...
const NodeView = (props: { node: Node }) => {
useEffect(() => {
autorun(() => {
console.log(`(autorun) ${props.node.label} is expanded: ${props.node.expanded}`);
// WRONG: when the node is a child of another node, this runs, but the NodeView is not re-rendered
});
}, []);
console.log(`rerendering ${props.node.label} `);
return (
<>
<div
className="nodeHeader"
onClick={action(() => {
props.node.expanded = !props.node.expanded;
})}
>
{props.node.label}
</div>
{props.node.expanded && (
<div className="row">
<div className="offset" />
<div className="column">
{props.node.children.map((child, index) => (
<NodeView key={index} node={child} />
))}
</div>
</div>
)}
</>
);
};
export default memo(observer(NodeView));
Here is how observable objects are created:
...
interface Node {
label: string;
expanded: boolean;
children: Node[];
nonObservedStuff: any;
}
const turnIntoObservable = (node: Node) => {
const children = node.children.map((child) => turnIntoObservable(child));
let observableNode: Node = { ...node, children };
// i'm trying to make observable not all, but only some specific properties
observableNode = makeObservable(observableNode, {
expanded: true,
children: observable.shallow
});
return observableNode;
};
let rootNodes: Node[] = ...
rootNodes = rootNodes.map((node) => turnIntoObservable(node));
Why changing state of an observable, that is linked by another observable in it's properties, does not cause to re-render of a component, that observes that observable?
You are exporting observer
component as a default one (export default memo(observer(NodeView));
), but inside NodeView
itself you are using non-observer NodeView
You need to wrap it with observer right away, so recursive version will be reactive too:
const NodeView = observer((props: { node: Node }) => {}
Also, memo
is applied automatically for all observer
components, you don't need to manually reapply it.