7

In the MobX with React docs, in the Side effects and observables section there is a receipe to respond to changes inside a useEffect hook.

import React from 'react'
import { autorun } from 'mobx'

function useDocumentTitle(store: TStore) {
  React.useEffect(
    () =>
      autorun(() => {
        document.title = `${store.title} - ${store.sectionName}`
      }),
    [], // note empty dependencies
  )
}

The example combines React.useEffect with mobx.autorun (but it could be mobx.reaction), but I can not see the benefit of autorun in the code. Once we are inside useEffect we can track our dependencies in the dependency array. The code is more clear, there is no need to dispose() and useEffect has a clear dependency array with what is needed.

import React from 'react'
import { autorun } from 'mobx'

function useDocumentTitle(store: TStore) {
  React.useEffect(() => document.title = `${store.title} - ${store.sectionName}`
  ,[store.title, store.sectionName])
}

Is there any reason to follow the example as given?

Here is a Code Sandbox

David Casillas
  • 1,801
  • 1
  • 29
  • 57
  • Have you checked if the version without autorun works? – jayarjo Oct 10 '19 at 06:41
  • Yes, I have included a code sandbox link in the post. – David Casillas Oct 10 '19 at 09:10
  • 1
    Not a best case to test this on. From what I see it will redraw anyway, 'cause parent is updating too. – jayarjo Oct 10 '19 at 18:46
  • 1
    Thanks, you were right. I have updated the example to avoid a parent reload on store change and find a difference between the two approaches. Using `autorun` inside `useEffect` the side effect is executed with no reload of the component while using the `useEffect` only approach runs a reload of the component on every update. – David Casillas Oct 11 '19 at 05:26

1 Answers1

6

autorun creates an observer, which means it will watch for any changes in store.title and store.sectionName, and automatically run whenever they change.

Setting it up in useEffect ensures that the autorun observer is only created once, and removed when the component is unmounted. Note that useEffect doesn't actually run when the store value changes; its effect is that autorun is (un)registered when the component (un)mounts.

Your second example without autorun would still run the effect and thus update the title if this component is re-rendered by other means, either because a parent component triggers a re-render, or if this component is wrapped in an observer, as in the Sandbox example:

function Test(props) {
  // 2. rerendered by observer when the value changes

  useEffect(() => {
    // <-- 3. execute effect because (2) rendered with a different dependency
  }, [props.store.size]); // <-- 1. observer notes this value is accessed

  return ( ... );
}

// <-- 1. observe any store value that is accessed in the Test function
export default observer(Test);

(edited for clarification)

Petr Bela
  • 8,493
  • 2
  • 33
  • 37
  • `store.title` and `store.sectionName` should be observed by just including them in the `useEffect` dependency array, there is no need that a parent observes them. I do not clear understand your point. – David Casillas Jan 20 '23 at 10:27
  • @DavidCasillas I believe you already answered your own question https://stackoverflow.com/questions/58306057/using-react-useeffect-and-mobx-autorun-together/65123050#comment103026373_58306057. tl;dr When title/sectionName changes in the store, `useEffect` doesn't trigger a rerender. `autorun` does. For the `useEffect` to run, something else would have to cause a rerender. – Petr Bela Jan 21 '23 at 11:46
  • Both aproaches work, and there is no parent observing the value. I interpret from your comment that the second approach does only work if a parent is observing the value, so is best to use the first, but this is not what I see. The only benefit I can tell is that `autorun` executes without the need to reload the component where it is defined, while with no autorun the whole component is reloaded to run the `useEffect`. If you inspect the logs the autorun example only logs inside the `autorun`, while the other logs inside the `useEffect`and in the component body. – David Casillas Jan 22 '23 at 20:31
  • If you're referring to the Sandbox example, the reason `useEffect` is called in the `Test` component is because the Test component is observed with `export default observer(Test);`. That means that mobx observes changes to the value of store.size in the `Test` function, causing a rerender of the Test component. On the contrary, although `TestAutorun` is also wrapped in an `observer`, the function itself doesn't access the store.size property directly, therefore the component doesn't rerender on change. However, the store.size value is accessed in autorun, which reruns on its own. – Petr Bela Jan 24 '23 at 10:17