Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Asserting about intermediate states when sequencing with useEffect #433

Closed
david-crespo opened this issue Aug 17, 2020 · 2 comments
Closed
Labels
question Further information is requested

Comments

@david-crespo
Copy link

This may be related to #241 and #393, but it might be different.

If these are separate renders, why can't I assert about them separately?

When using useEffect to sequence events, it seems act causes all the updates to be batched into one. Often this is what you want, but what if I want to assert about the intermediate states here, specifically the moment where a == true and b == false?

function useHook() {
  const [a, setA] = useState(false);
  const [b, setB] = useState(false);

  console.log({a, b});
  // { a: false, b: false }
  // { a: true, b: false }
  // { a: true, b: true }
  // so `setA` is causing one render, and `setB` is causing a second render, as expected

  useEffect(() => {
    if (a) setB(true);
  }, [a]);

  return {a, b, run: () => setA(true)};
}

it("updates a and then b", () => {
  const {result} = renderHook(() => useHook());
  act(() => result.current.run());

  // can't do:
  // expect(result.current.a).toBe(true);
  // expect(result.current.b).toBe(false);

  expect(result.current.b).toBe(true);
});

Same issue but now with async setState

If I wrap the initial setA in a setTimeout to make it async (which more resembles the real code I'm basing this on) we need waitForNextUpdate to get any result. This makes sense to me, but it opens up an even bigger space in which it seems like I should be able to assert a == true and b == false.

function useHook() {
  const [a, setA] = useState(false);
  const [b, setB] = useState(false);

  useEffect(() => {
    if (a) setB(true);
  }, [a]);

  const run = () => {
    setTimeout(() => {
      setA(true);
    });
  };

  return {a, b, run};
}

it("updates a and then b", async () => {
  const {result, waitForNextUpdate} = renderHook(() => useHook());
  act(() => result.current.run());

  // I want to assert a == true and b == false here

  await waitForNextUpdate();
  expect(result.current.b).toBe(true);
});
@david-crespo david-crespo added the question Further information is requested label Aug 17, 2020
@mpeyper
Copy link
Member

mpeyper commented Aug 18, 2020

Hi @david-crespo,

I'm not sure what to tell you as this is the expected behaviour when using act to trigger updates to the hook's state. act will wait for all the batched updates from successive renders to flush before moving on (at least that's my understanding of what it does).

You can get see the intermediate states in your test if you remove the act and wait for the changes yourself, but you will be met by a big warning in your console from react about not doing that:

it('updates a and then b', async () => {
  const { result, waitForNextUpdate } = renderHook(() => useHook())
  result.current.run()

  expect(result.current.a).toBe(true)
  expect(result.current.b).toBe(false)

  await waitForNextUpdate()

  expect(result.current.b).toBe(true)
})

From my perspective, I'm not sure what value you gain from testing that one went true before the other and the timing of those events. It feels very much like an implementation detail of your hook and what you really care about is the end state anyway. Do you care that a was true immediately after run was called and b followed a single render frame later, or that both a and b we true when the rendering was complete?

In any case, there is not much I can do to change the behaviour of act or the warnings react produces when you don't act for updates, but if it is important enough to you, you could try opening an issue in their repo and see what options they can give you.

@mpeyper mpeyper closed this as completed Aug 18, 2020
@david-crespo
Copy link
Author

david-crespo commented Aug 18, 2020

Thanks for the answer. I agree that it's at best dubious that I would want to assert about that state. The real code that inspired this question was a create and poll hook that makes a POST request and then polls with GETs for progress. So the initial POST coming back would trigger the first GET. Before the GET comes back, it would be in a state analogous to a == true, b == false, so I wanted to assert something about that. But I can live without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants