FormidableLabs/react-ssr-prepass

useState initializer called multiple times with suspense

bmathews opened this issue · 5 comments

it('should only call useState initializer once across suspensions', () => {
  let counter = 0;

  const initializeState = jest.fn(() => counter += 1);

  const Inner = jest.fn(props => {
    expect(props.value).toBe(2)
  })

  const Outer = jest.fn(() => {
    const [] = useState(initializeState);

    // Throw promise on first pass, render on 2nd pass
    if (counter === 1) {
      counter += 1;
      throw Promise.resolve()
    }

    return <Inner value={counter} />
  })

  expect(counter).toBe(0)
  return renderPrepass(<Outer />).then(() => {
    expect(initializeState).toHaveBeenCalledTimes(1);
    expect(counter).toBe(2)
  })
})

I wrote a failing test here that is expecting the state initializer to only be called once during render. I had a similar issue with useMemo(fn, []) calling fn multiple times.

Any thoughts on that?

Ephem commented

Nice catch! I’m pretty sure though that React itself gives no guarantees these will only be called once across suspends. See for example this issue.

Thanks @Ephem ! While that behavior is unfortunate, it sounds like it's the 'correct' behavior at the moment.

Ephem commented

Yeah, it confused me at first too. 😀 It’s equally correct to memoize across suspends of course, so it’s definitely possible to implement this as an optimization!

This'd be nice, as it would allow executing and caching suspense promises on mount

@achung89 You should avoid caching suspense promises or other “global loading state” in a single component’s state. This may cause subtle bugs that are then hard to iron out. Chances are you may want to have a shared cache across context if you’re doing this, but of course, libraries exist to make it easier to handle fetching logic, although some don’t play nicely with suspense.

If you really need state across suspenses in react-ssr-prepass then maybe try using a ref. It’s worth noting though that React itself doesn’t usually preserve any state across suspense.