-
What is the best way to send React component props to In MobX 5 you would pass the props via the // Child.store.js
export const ChildStore = (source) => ({
parentStore: source.parentStore,
get myGetter() {
return this.parentStore.someProperty.map(/* do complicate stuff */);
},
}); // Child.jsx
export const Child = observer((props) => {
// passing props via the source parameter of useLocalStore
const childStore = useLocalStore(ChildStore, {
parentStore: props.parentStore,
});
return <div>{childStore.myGetter.map(/* render out components */)}</div>;
}); In MobX 6 I think the solution is to use // Child.store.js
export const ChildStore = () => ({
parentStore: null,
get myGetter() {
if (!this.parentStore) {
return [];
}
return this.parentStore.someProperty.map(/* do complicate stuff */);
},
init(parentStore) {
this.parentStore = parentStore;
},
}); // Child.jsx
export const Child = observer((props) => {
const childStore = useLocalObservable(ChildStore);
useEffect(() => {
childStore.init(props.parentStore);
}, []);
return <div>{childStore.myGetter.map(/* render out components */)}</div>;
}); The solution I want to validate with the mobx community is to wrap my store with another function. This way I can pass in the component props when the store gets created within // Child.store.js
export const ChildStore = (storeProps) => {
return () => ({
parentStore: storeProps.parentStore,
get myGetter() {
return this.parentStore.someProperty.map(/* do complicate stuff */);
},
});
}; // Child.jsx
export const Child = observer((props) => {
const childStore = useLocalObservable(
ChildStore({
parentStore: props.parentStore,
})
);
return <div>{childStore.myGetter.map(/* render out components */)}</div>;
}); I would love some feedback and/or other approaches. |
Beta Was this translation helpful? Give feedback.
Replies: 5 comments 11 replies
-
There is nothing indeed to forbid you to pass the props to For background, the reason we removed the original pattern is that it requires prop syncing to be part of render, making render side-effectful, which React (rightly) complained about. By using useEffect, it becomes much clearer when the syncing happens, and it plays according to the React rules (sorry, cant link to the original issue easily afaik, as we're merging the issue trackers) |
Beta Was this translation helpful? Give feedback.
-
Please for anyone writing docs or articles: If you've considered the above and you still want to init/sync state from props, there is nothing special about mobx: const [store] = useState(() => new Store(props.foo, props.bar)) Sync local state with props const [localStore] = useState(() => new Store());
localStore.foo = props.foo;
localStore.bar = props.bar; Sync any state with props const [store] = useState(() => new Store()); // the store could also come from props/import whatever
// layoutEffect!!!
useLayoutEffect(() => {
store.foo = props.foo;
store.bar = props.bar;
}) Other recommendations:
|
Beta Was this translation helpful? Give feedback.
-
Thanks for the feedback. Seems like calling the Let me give a little more details on why I asked to question in the first place in hopes I can squeeze a little bit more knowledge/guidance out you. Typically I have one local store per page to manage everything with a few global stores to facilitate data across pages like auth/user stuff. But there is this dashboard page with several widgets/ui-containers on it. To keep with the examples above I have this If you have any other tips/tidbits/documentation you'd like to share I'll take it or give me a thumbs up if I am headed in the right direction. |
Beta Was this translation helpful? Give feedback.
-
Not "and" - the point of lifting state up is to remove the need for syncing.
It's the worst possible way. If you know that If you want to account for a situation where const [childStore, setChildStore] = useState();
useLayoutEffect(() => {
setChildStore(new ChildStore(props.parentStore))
}, [props.parentStore]) b) Update the const [childStore] = useState(new ChildStore())
useLayoutEffect(() => {
childStore.setParentStore(props.parentStore)
}, [props.parentStore]) The second one is trickier, because it may involve invalidating a lot of things in |
Beta Was this translation helpful? Give feedback.
-
@codeBelt function useLocalStore<TStore extends Record<string, any>, TSource extends object = any>(
initializer: () => TStore,
props?: TSource,
annotations?: AnnotationsMap<TStore, never>
): TStore & {
props: TSource;
} {
const r = useRef(null as TSource);
r.current = props;
const state = useState(function () {
const ret = initializer();
Object.defineProperty(ret, "props", {
enumerable: false,
get() {
return r.current;
}
});
if (!annotations) {
annotations = {};
}
(annotations as any)["props"] = false;
return observable(ret, annotations, { autoBind: true });
})[0];
return state as any;
}
const ParentComponent = observer(function () {
const state = useLocalObservable(() => {
return {
count: 1
};
});
return (
<div>
ParentComponent count: {state.count}
<button
onClick={() => {
state.count += 1;
}}
>
+
</button>
<hr />
<ChildComponent count={state.count} />
</div>
);
});
const ChildComponent = observer(function (props: { count: number }) {
const state = useLocalStore(() => {
return {
count: 1,
printParentCount() {
console.log(state.props.count);
}
};
}, props);
return (
<div>
ChildComponent count: {state.count}
<button
onClick={() => {
state.count += 1;
}}
>
+
</button>
<div>prop count:{props.count}</div>
<button
onClick={() => {
state.printParentCount();
}}
>
print parent count
</button>
</div>
);
}); |
Beta Was this translation helpful? Give feedback.
Not "and" - the point of lifting state up is to remove the need for syncing.
There is no need to create widget store lazily on component mount and then keeping it in sync with props.
Whether or not some widget component will be rendered depends on state. So the state knows upfront which widget stores are needed.
So you can create the widget store instances inside constructors/actions upfront and pass them to components with ctx/props.
Eg you could have some
DashboardPageStore
which creates an array of widget stores in constructor and then just pass them to widget components.