react-use icon indicating copy to clipboard operation
react-use copied to clipboard

`useMethods` methods object is not stable and changes when `initialState` is changed

Open BenJenkinson opened this issue 4 years ago • 3 comments

What is the current behavior?

The useMethods hook wraps the behaviour of useReducer to return a nicely typed methods object.

https://github.com/streamich/react-use/blob/0fabbcd641e5fc6d16091e7c4c803efe80d7ff56/docs/useMethods.md#L44

However, the methods object returned from useMethods is not stable and changes whenever the initialState argument changes.

This does not match how React treats an initialState argument for useState and useReducer

The initialState argument is the state used during the initial render. In subsequent renders, it is disregarded. (https://reactjs.org/docs/hooks-reference.html#lazy-initial-state)

This also does not match how React guarantees the setState method from useState and the dispatch method from useReducer to both be stable and not to change on re-renders or when initialState changes.

What is the expected behavior?

I would expect that the methods object was guaranteed to be stable and not to change on re-renders or when initialState changes. (Though I would expect it to change if the createMethods argument changed)

This would then match the behaviour of the setState method from useState and the dispatch method from useReducer.

Investigation

Looking at the source of useMethods.ts, I think the current behaviour is unintentional.

For example, the dispatch behaviour is already stable, since createMethods is re-run for each dispatch call:

https://github.com/streamich/react-use/blob/0fabbcd641e5fc6d16091e7c4c803efe80d7ff56/src/useMethods.ts#L19-L24

But the way that the wrappedMethods object is created introduces a dependency on initialState:

https://github.com/streamich/react-use/blob/0fabbcd641e5fc6d16091e7c4c803efe80d7ff56/src/useMethods.ts#L28-L35

I think the bug was introduced accidentally in https://github.com/streamich/react-use/commit/b394f3d72356d331dbce48acd3686bbb64d331b5, since it wasn't present in the original version of the hook.

Version Info

  • OS: Windows 10
  • Browser: Chrome 83
  • React: 16.13.1
  • react-use: 15.2.1, >=13.26.0

BenJenkinson avatar Jun 08 '20 12:06 BenJenkinson

Faced a simillar issue, where the methods object was changing on each render because of the initialState.

function SomeProvider({ children, data }) {
  const [state, methods] = useMethods(createMethods, { ...INITIAL_STATE, ...data });

Was able to fix it by memoizing the initialState as

function SomeProvider({ children, data }) {
  const initialState = useMemo(
    () => ({ ...INITIAL_STATE, ...data }),
    [data]
  );
  const [state, methods] = useMethods(createMethods, initialState);

tiwariav avatar Jun 13 '21 18:06 tiwariav

Just stumbled upon this. Seems like a basic design flaw, initialState value shouldn't be tracked.

danguilherme avatar Mar 30 '22 14:03 danguilherme

Also affected.

dbousamra avatar Oct 20 '22 07:10 dbousamra

Looks like they fixed this in the master:

https://github.com/streamich/react-use/blob/master/src/useMethods.ts

vin-mfvhn avatar Dec 20 '22 03:12 vin-mfvhn

Nope, initialState is still being tracked for the creation of the methods: https://github.com/streamich/react-use/blob/325f5bd69904346788ea981ec18bfc7397c611df/src/useMethods.ts?rgh-link-date=2022-12-20T13%3A34%3A27Z#L36

It was added in Feb 2020 for some reason: https://github.com/streamich/react-use/commit/b394f3d72356d331dbce48acd3686bbb64d331b5

danguilherme avatar Dec 20 '22 13:12 danguilherme

I opened a PR fixing it: https://github.com/streamich/react-use/pull/2458

danguilherme avatar Dec 20 '22 14:12 danguilherme