5

My component has two Rect.useEffect hook

const Example = ({ user }) => {
  React.useEffect(() => {
    autorun(() => {
      console.log("inside autorun", user.count);
    });
  });

  // Only runs once
  React.useEffect(() => {
    console.log("Why not me?");
  });

  return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};

I update this component using mobx. It is re-rendered correctly. But "Why not me?" is printed only once.

As per official docs

By default, effects run after every completed render

This means console.log("Why not me?"); too should run every time prop user is updated. But it doesn't. The console output is this

What's the reason behind this apparent inconsistency?

My complete code can be viewed here

Edit mobx-useEffect query

Andrew-Dufresne
  • 5,464
  • 7
  • 46
  • 68

2 Answers2

6

In Mobx, just like Observer component which provides a render function callback, autorun function also executes independently of the react lifecycle.

This behaviour happens because you have user count as a observable variable.

According to the mobx-react docs

Observer is a React component, which applies observer to an anonymous region in your component. It takes as children a single, argumentless function which should return exactly one React component. The rendering in the function will be tracked and automatically re-rendered when needed.

and mobx docs

When autorun is used, the provided function will always be triggered once immediately and then again each time one of its dependencies changes.

You can confirm this behvaior by logging directly inside the functional component and you will observer that the component is only rendered once

EDIT:

To answer your question

If I change useEffect to this

React.useEffect(autorun(() => {console.log("inside autorun", user.count)}));

basically remove anonymous function from useEffect and just pass autorun directly, then it is run only once. Why is it so? What's the difference?

The difference is that autorun returns a disposer function which when run will dispose of the autorun and would not longer execute it.

From the docs:

The return value from autorun is a disposer function, which can be used to dispose of the autorun when you no longer need it.

Now what happens is that since useEffect executes the callback provided to it when it runs, the callback executed is the disposer function returned by autorun which essentially cancels your autorun.

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • That's a great explanation. If I change `useEffect` to this `React.useEffect(autorun(() => {console.log("inside autorun", user.count)}));`, basically remove anonymous function from `useEffect` and just pass `autorun` directly, then it is run only once. Why is it so? What's the difference? – Andrew-Dufresne Apr 01 '19 at 09:46
  • 1
    @Andrew-Dufresne Updated the answer with an explanation – Shubham Khatri Apr 01 '19 at 10:10
3

It looks like your component doesn't rerender. autorun receives a callback and might call it independently from render.

Example component rerenders only when its parent rerenders or when its props change.

Use this code to observe what's really happening:

const Example = ({ user }) => {
  console.log('render');
  React.useEffect(() => {
    console.log('useEffect autorun');
    autorun(() => {
      console.log("inside autorun", user.count);
    });
  });

  // Only runs once
  React.useEffect(() => {
    console.log("Why not me?");
  });

  return <Observer>{() => <h1>{user.count}</h1>}</Observer>;
};
UjinT34
  • 4,784
  • 1
  • 12
  • 26