Skip to content

Fix race conditions and add tests #85

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

Merged
merged 4 commits into from
May 13, 2021
Merged

Fix race conditions and add tests #85

merged 4 commits into from
May 13, 2021

Conversation

compulim
Copy link
Owner

@compulim compulim commented May 13, 2021

Fix #84.

(This PR will also move the default branch from master to main.)

Changelog

Added

  • Added a test harness, in PR #85

Fixed

  • Fixed #84. Fixed a race condition: while under heavy load, sticky, and at the end, calling useScrollTo() to any positions, the scroll view may scroll back to the bottom immediately, in PR #85

Description

We are using a state value (i.e. useState()) in a setInterval().

If the system is under heavy load, our stickiness check that runs every 17 ms may get a outdated sticky value.

We should not use any state value in setInterval, setTimeout, and useCallback. Instead, we should use ref value.

This work will introduce a new hook called useStateRef, which return [value, setValue, valueRef] and works similar to useState() plus a handy ref value.

Root cause

This is a sample repro.

const App = () => {
  const [value, setValue] = useState(1);

  useEffect(() => {
    const interval = setInterval(() => console.log('value: ', value), 10);

    return () => clearInterval(interval);
  }, [value]);

  const onClick = useCallback(() => {
    setValue(value => {
      console.log('onClick: ', value + 1);
      return value + 1;
    })
  }, [setValue]);

  return <button onClick={onClick}>Click me</button>;
};

Under heavy load, after clicking the button. The console log may show:

value: 1
onClick: 2
value: 1 <-- Note this line
value: 2

Although value changed, under heavy load, React may postpone calling App() to next frame since React Fiber can only run for 17 ms.

Thus, the useEffect is not called and interval was not reset thru clearInterval followed by setInterval.

Thus, when the interval function runs, it is still using the old value, which is 1 (on the denoted line).

To fix it, we should use ref value in the interval function. This also helps with performance because the interval function was not re-registered.

const App = () => {
  const [value, setValue, valueRef] = useStateRef(1);

  useEffect(() => {
    const interval = setInterval(() => console.log('value: ', valueRef.current), 10);

    return () => clearInterval(interval);
  }, [valueRef]);

  const onClick = useCallback(() => {
    setValue(value => {
      console.log('onClick: ', value + 1);
      return value + 1;
    })
  }, [setValue]);

  return <button onClick={onClick}>Click me</button>;
};

@compulim compulim merged commit 0f0a251 into main May 13, 2021
@compulim compulim deleted the fix-race branch May 13, 2021 23:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Race condition between sticky and useScrollTo
1 participant