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

Callback props don't seem to update with state changes #947

Closed
AlexKDawson opened this issue Mar 8, 2023 · 5 comments
Closed

Callback props don't seem to update with state changes #947

AlexKDawson opened this issue Mar 8, 2023 · 5 comments

Comments

@AlexKDawson
Copy link

AlexKDawson commented Mar 8, 2023

Repro: https://codesandbox.io/s/focus-trap-react-issue-forked-jgei7y?file=/src/App.js

Hello! Thanks for maintaining this library, I just discovered it recently and it's been a huge help. I have however run into this one issue, I believe its a bug but may just be something I'm doing wrong.

In short, when I pass callbacks into the onDeactivate/onPostDeactivate/clickOutsideDeactivates if those callbacks use parent state, the callbacks seem to never be updated past the first render. In general it seems like no updates to the focusTrapOptions trigger a rerender of the FocusTrap at all after initial render. (with the exception of the call to setActive in my example above) but I suspect this is due to active being passed in as its own prop.

For the repo above:

Expected behavior:
num is initialized to 0
The app renders the number 100 after the useEffect is triggered.
Activating then deactivating the Focus Trap should cause the number 100 to be logged as the most recent value of num.

Actual behavior:
num is initialized to 0
The app renders the number 100 after the useEffect is triggered.
Activating then deactivating the Focus Trap logs 0 to the console.

@stefcameron
Copy link
Member

@AlexKDawson I'm glad you're finding the library useful! 😄

I can't say I'm surprised by this now that you've brought up the issue related to React hooks. focus-trap-react was created (before React hooks were introduced in React v16.8) as a React wrapper for focus-trap which has nothing to do with React -- especially not functional React using hooks where you might pass an event handler tied to state which gets created on every render or created whenever that state changes (if you useCallback() or useMemo() to generate the handler).

All those handlers are properties of the focusTrapOptions prop, and those options get passed into focus-trap internally. And once the trap is created, the focusTrapOptions is completely ignored because there's no way to update the created trap with a new set of options. That would require a big update to focus-trap itself to make it possible.

So it's really a one-time thing once the trap has been created. And looking at the code, the focusTrapOptions prop can really only be provided once on first render. It's never looked at again. One of the main reasons for that (and I'm just speaking to how the code was/is after I became the maintainer a few years ago) is that the <FocusTrap> component creates its own internal set of focus-trap options and then iterates the focusTrapOptions object's properties and picks it apart. So even the (potentially) shared object reference is lost.

I believe this works fine with classical React (i.e. class-based where you use a JS class to define a component), however, because the component's state is a class member, and provided you pass handlers bound to the class instance to focusTrapOptions, you'll always have access to the latest state via this.state... in your handlers. You can see how this works in the main code example.

So my advice here would be to either switch to a class for the component that has state and which uses <FocusTrap> (which, I understand, might be a "fly in the ointment" in a code base that uses functional components 😉 ), or -- but this probably makes things a lot harder to manage -- useRef({ foo: 1, bar: 2 }) for the bits of state that need to be accessed by the handlers you're giving to the trap.

If you concur with this, then I'll add a warning about this in the focusTrapOptions prop docs for posterity.

@AlexKDawson
Copy link
Author

AlexKDawson commented Mar 8, 2023

Thanks again for being so responsive and explaining this.

That all makes sense to me, I think a warning in the docs would be beneficial!

I thought some on the two approaches you suggested and I think I'm going to go with the latter albeit potentially more risky approach for my specific use case. I think using the example below I can combine the useRef with the existing useState and a useEffect hook to make a copy of the state object on each render inside the ref. As long as I only pass the ref values into any focusTrap callbacks, there shouldn't be any additional difficulty.

That said, I could see if the state starts to get a bit more complex than one or two read only variables this approach could get out hand quickly 🙂 But I'll keep that in mind!

https://codesandbox.io/s/focus-trap-react-issue-forked-oj47v1?file=/src/App.js

@stefcameron
Copy link
Member

Thanks again for being so responsive and explaining this.

You're welcome!

That all makes sense to me, I think a warning in the docs would be beneficial!

Warning has been added!

I thought some on the two approaches you suggested and I think I'm going to go with the latter albeit potentially more risky approach for my specific use case. I think using the example below I can combine the useRef with the existing useState and a useEffect hook to make a copy of the state object on each render inside the ref. As long as I only pass the ref values into any focusTrap callbacks, there shouldn't be any additional difficulty.

That said, I could see if the state starts to get a bit more complex than one or two read only variables this approach could get out hand quickly 🙂 But I'll keep that in mind!

https://codesandbox.io/s/focus-trap-react-issue-forked-oj47v1?file=/src/App.js

Sounds good, and thanks for sharing the sandbox for posterity!

@stefcameron
Copy link
Member

@all-contributors add @AlexKDawson for docs

@allcontributors
Copy link
Contributor

@stefcameron

I've put up a pull request to add @AlexKDawson! 🎉

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

No branches or pull requests

2 participants