mobx: Execute autorun not work when updating storage in the react context

Intended outcome: Execute autorun when store is updated in react context

Actual outcome: not work

How to reproduce the issue: https://codesandbox.io/s/ceshigechongku-v2ypj?file=/src/App.js:1645-1647

The key code

--------global store and Context-----
  const store = useLocalObservable(() => new MyState({}));
  React.useEffect(() => {
    store.init();
  }, []);

  return (
    <singleSelectStoreContext.Provider value={store}>
      <Wrap />
    </singleSelectStoreContext.Provider>
  );
------ A child component of <Wrap /> -------------
  const rootStore = useSingleSelectStoreContext();
  const state = useLocalObservable(() => ({
    optionArr: [],
    init(val) {
      state.optionArr = val;
    }
  }));
  React.useEffect(
    () =>
      autorun(() =>
       // There's runInAction question here, and not wrapping it with an A would warn me,
      //  It's like  here.      https://github.com/mobxjs/mobx/issues/2718
        runInAction(() => {
          // The reason why I'm doing this is because I don't want to break the data in the global store
          state.init(rootStore.initList);
        })
      ),
    []
  );

Versions mobx: 6.1.5 mobx-react: 7.1.0

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 16 (6 by maintainers)

Most upvoted comments

React.useEffect(() => {
  state.init(rootStore.initList);
}, [rootStore.initList]);

Won’t work correctly unless initList is immutable (considering you want to re-run effect when initList changes)

I don’t know how to replace it now?

You shouldn’t need to synchronize some “local state” with some “global state”. It’s antipattern. You should just move the local state to the global. https://github.com/mobxjs/mobx-react-lite/pull/302#issuecomment-678685286 https://github.com/mobxjs/mobx/issues/2710#issuecomment-759354434

coupling will be increased

It won’t, because the coupling already exists, otherwise you wouldn’t need to synchronize. You just want to move the coupling to some synchronization layer, but that itself won’t remove the coupling. You just make the application more complicated - it’s very likely that the “synchronized” component will never be used standalone outside of your application, therefore there is no practical reason to keep it “decoupled” and introduce some additional abstraction to keep it in sync. And even if you would need that, you have hooks - they can be easily moved around and plugged into any component, which allows you to do these refactors easily - eg if you have some complex component logic that depends on state, you don’t need to know where the state comes from - whether it’s global/local/props - you just pass the state as params to hook. Forget about mobx and observables and think how you would solve this with react alone - you want to copy state of some parent component into a state of a child component - it’s doable with useLayoutEffect/getDerivedStateFromProps/componentDidUpdate, but you will be always advised to lift the state up instead.

give up local state

It’s not giving up local state - but you use local state for state that isn’t truly local.

Btw if you don’t need to keep the local state synchronized, but simply initialize it from global when template switches, then, well I would advise to initialize the template state in the action that changes the template, but you can also have a ATemplate/BTemplate/etc component that is swapped with template and initializes it’s own state on mount (or eventually in layout effect that depends on some template id).

EDIT: If you want to “hack” it, there is _allowStateChanges, which you can use instead of runInAction, that will supress the warning.

runInAction won't track anything

2021-02-19 23 16 20

Don’t use runInAction in autorun, it not work as your expected. It’s the final conclusion。

React Hooks Deps

// []  Removed to make 'useEffect' every time it is executed

No, don’t do it. https://stackoverflow.com/questions/58579426/in-useeffect-whats-the-difference-between-providing-no-dependency-array-and-an

Fast Chinese Reply

我举的例子是想说明为什么你的autorun没有按照你设想的方式去执行,因为是子组件,所以按照react lifecycle第一次执行的时候initList是空的,而且runInAction是不会收集依赖的[runInAction won't track anything]. 等于说你的autorun没有track到任何可观测的依赖.之后你的rootStore值变更不会响应到autorun。至于你去掉dep数组参数,建议你看看React官方文档,这样等于每次re-render都执行。最后你应该是想在子store里使用Global Store的数据,但是只是初始化,初始化之后子store和Global Store就没有关系了。 ⚠️ 在你最初的想法里,其实逻辑也不对。如果autorun真的按照你设想的工作了,那岂不是Global Store每次变更initList,你的子store都会执行初始化?

solution for u

I know what you want to do.

And I’m not changing the Global Store , I’m just initializing the C Store.

Maybe you forgot the useRef in React hooks world ?

+ const localRef = useRef(false);
  const rootStore = useSingleSelectStoreContext();
  const state = useLocalObservable(() => ({
    optionArr: [],
    init(val) {
      state.optionArr = val;
    }
  }));
  React.useEffect(
    () => {
+     if (!localRef.current && rootStore.initList.length > 0) {
+       localRef.current = true;
+       state.init(rootStore.initList);
      }
    },
+   [state, rootStore.initList]
  );

It works as expected in your codesandbox example.

runInAction won’t track anything, if you want to track store.name you have to access it outside the runInAction. I didn’t entirely get what the OP tried to achieve, but make sure to check https://mobx.js.org/react-integration.html#useeffect if you want to set up an effect that listens to observables

On Thu, Feb 18, 2021 at 1:51 PM ChenLei notifications@github.com wrote:

reproduction

Minimal reproduction code example for your case.

https://jsfiddle.net/ichenlei/t28mrcqp/17/

[image: 2021-02-18 21 49 03] https://user-images.githubusercontent.com/14012511/108366258-21bf0f00-7233-11eb-9adb-533c17683dfa.png summary

It looks like when you nest call runInAction in autorun, it works not as your expected. I try to debug it and I found that autorun tracked zero deps in your case. But maybe it’s expected behavior in mobx concepts. Need Mr. Michel Weststrate to explain it.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/mobxjs/mobx/issues/2816#issuecomment-781357265, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAN4NBEHER6I6UC3W4KV6FLS7ULNZANCNFSM4X2C73PA .