React: old state values in useEffect1

I’m a beginner in React as well as Rescript (experienced with Vue, Elm, etc.). I was trying to use useEffect1 with a single dependency, but ran into an issue that when the effect runs, I see the old value of the dependency—ie. the value before the state changed and triggered the re-render.

This is the case even for a minimal example:

module Counter = {
  @react.component
  let make = () => {
    let (count, setCount) = React.useState(() => 0)

    React.useEffect1(() => Some(() => Console.log2("count", count)), [count])

    let increment = () => setCount(c => c + 1)

    <button type_="button" className="action" onClick={_ => increment()}>
      {React.int(count)}
    </button>
  }
}

The console shows 0 when the component mounts, as expected, but on clicking the button once, it renders 1 but another 0 is printed to the console. Click again, 2 is rendered and 1 is logged.

I tried comparing to the exact same example in js and it behaves as expected. So… is this a bug in rescript-react? It makes it very difficult to use useEffect if I can’t access the value that’s changed.

Environment

{
  "dependencies": {
    "@rescript/core": "^0.3.1",
    "@rescript/react": "^0.11.0",
    "react": "^18.2.0",
    "react-dom": "^18.2.0",
    "rescript-webapi": "^0.8.0"
  },
  "devDependencies": {
    "@jihchi/vite-plugin-rescript": "^5.3.0",
    "@vitejs/plugin-react": "^4.0.2",
    "rescript": "^10.1.4",
    "vite": "^4.4.2"
  }
}

Are you sure your JS example is the same? If you compile your useEffect call, it’s going to be smth. like

React.useEffect(() => () => console.log('count', count));

IOW, your effect callback does nothing as a direct effect, but returns (Some(...)) as a cleanup callback. That cleanup callback is called on the next render, before the next call of the effect, with previous values.

If you want to log the value from the current render, try:

React.useEffect1(() => {
  Console.log2("count", count)
  None // no cleanup
}, [count])
2 Likes

Ahhh, that makes so much sense! I was totally confused about why the callback wants me to return an optional callback. Of course, I’m using the cleanup function! I’m still getting to grips with arbitrary side effects, coming from a pure functional background, so when a signature asks me to return a callback instead of unit, I dutifully do so. :sweat_smile:

1 Like