I have a weird behavior of MobX.
Here is my store in the file store.tsx
. You can find it also as a sandbox.
import { action, makeObservable, observable} from "mobx";
export default class ViewStore {
currentProjectId: string | undefined = undefined;
constructor() {
makeObservable(this, {
currentProjectId: observable,
setCurrentProjectId: action,
});
}
setCurrentProjectId = (projectId: string | undefined) => {
this.currentProjectId = projectId;
};
}
And here is my component
import React from "react";
import { observer } from "mobx-react-lite";
import { useParams } from "react-router-dom";
import ViewStore from "./store";
interface Props {
localStore: ViewStore;
}
interface UrlParams {
projectId: string;
}
function AppManageProject({ localStore }: Props) {
const urlParams = useParams<UrlParams>();
if (localStore.currentProjectId !== urlParams.projectId) {
localStore.setCurrentProjectId(urlParams.projectId);
return <></>;
}
return (
<div>{localStore.currentProjectId}</div>
);
}
export default observer(AppManageProject);
The page is part of a larger app and it's reached from the route /<project id>
. When you first browse to e.g. /prj1
, the if block is executed, so the observable currentProjectId
is set to "prj1" via the MobX action setCurrentProjectId
. I expect the change of the observable to trigger a rerendering, where now urlParams.projectId === localStore.currentProjectId
. But the component is actually not rerendered.
In the sandbox, from the printout in the console, you can se that you do enter in the if block, but the rerendering is not triggered.
Notice that if instead of calling the action setCurrentProjectId
directly, I use a callback, e.g. setTimeout(() => localStore.setCurrentProjectId(urlParams.projectId), 0)
then everything works.
Thinking that the callback works because it uses an arrow function, I also tried to replace
localStore.setCurrentProjectId(urlParams.projectId)
with a call to an arrow function:
( () => localStore.setCurrentProjectId(urlParams.projectId) ) ()
But this also doesn't work.
Does anyone have any explanation/solution of what is going on? Thank you
For some reason MobX does not rerender components if you update some observable right inside of the render function. I don't know exactly why, but side effects inside render function is a bad pattern anyway, so it's kinda makes sense that it does not work.
What you can do to make you code more idiomatic is to use useEffect
hook for side effects, like that:
function AppManageProject({ localStore }: Props) {
const urlParams = useParams<UrlParams>();
useEffect(() => {
localStore.setCurrentProjectId(urlParams.projectId);
}, [urlParams.projectId]);
if (localStore.currentProjectId !== urlParams.projectId) {
return <></>;
}
return <div>{localStore.currentProjectId}</div>;
}