-
Notifications
You must be signed in to change notification settings - Fork 4.3k
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
New hook: useInstanceId #19071
New hook: useInstanceId #19071
Conversation
* Find an unallocated id. | ||
*/ | ||
function findId() { | ||
let id = allocatedIds.findIndex( ( allocated ) => ! allocated ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is slightly less performant than just increment the id, any concern about that?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not concerned as this only executes when a component mounts. Incrementing ids is simple, but it has no end. The highest number possible here is the amount of mounted components per page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withInstanceId
has counters for each component. I don't think we can do that with hooks. The counter would be per page load. So the number could potentially reach much higher than before. That's why I decided to reuse ids for unmounted components.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a clean up stack:
let nextId = 0;
const freedIds = [];
function findId() {
if ( freedIds.length ) {
return freedIds.pop();
}
return nextId++;
}
function freeId( id ) {
freedIds.push( id );
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That works too. Either you have a pool of allocated ids or a pool of free ids.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the stack is to avoid the linear lookup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, I’ll adjust it. Thanks!
export default function useInstanceId() { | ||
// Take advantage of useMemo to get the same id throughout the life of a | ||
// component. | ||
const id = useMemo( findId, [] ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems like this might be a use-case for useRef
instead?
The returned object will persist for the full lifetime of the component.
It’s handy for keeping any mutable value around similar to how you’d use instance fields in classes.
https://reactjs.org/docs/hooks-reference.html#useref
i.e.
const id = useRef( findId() );
// ...
return id.current;
Unclear whether it would also make it okay to avoid passing id
as the dependency of useEffect
when used this way, if we have a stronger guarantee that the value won't change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
id
itself would never change. id.current
would. But we should still pass id
, to align with the planned linting rules.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s ok if the id changes. With useRef, wouldn’t findId be called on every render?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, right, didn't pay attention to that part 😬
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With useRef, wouldn’t findId be called on every render?
Yes, you're right 👍 At a glance, I don't see a way around that, so maybe useMemo
is the way to go.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, using useMemo
for this is not recommended, because it's not always respected:
https://reactjs.org/docs/hooks-faq.html?no-cache=1#how-to-create-expensive-objects-lazily
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know, but as I said before, it's fine if it's not respected. Then a now id is given and the previous id is freed up. It doesn't do any harm, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, doesn't do much unless it happens often.
It could be a problem with async mode.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's see. If need be, we can create an alternative to useMemo
that never gets called again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good, a useStrictMemo
hook 😆
Build failure appears to be caused by this line:
I think it might have to change, since we can't rely on a specific ID anymore (it probably shouldn't have been written this way in the first place, basing it off a dynamic ID). It might work as... const postUrl = await page.$eval( '[id^="inspector-text-control-"]', ( el ) => el.value ); |
I have a feeling that if these IDs are now shared in a global scope, our snapshots will become increasingly unreliable, because they can be impacted by changes in the rendering behavior of unrelated components. It's as similar issue to what I expressed at #14995 (comment) . |
@aduth Right, they are shared globally. |
Co-Authored-By: Andrew Duthie <[email protected]>
@aduth I don't understand fully how the snapshot would become unreliable though. If everything is rendered in the same order within one test and the tests are isolated, then it shouldn't be a problem? |
Looks like we're going for #19091. Closing this. |
Description
Edit: I've now created a better alternative at #19091.
This hook is a replacement for
withInstanceId
.The implementation is a bit different. Instead of just continuously incrementing the id (number), it keeps track of the allocated ids. When the component unmounts, the id is free to be used again.
How has this been tested?
Screenshots
Types of changes
Checklist: