Skip to content

Conversation

@snowystinger
Copy link
Member

@snowystinger snowystinger commented Oct 17, 2025

Closes

This PR focused on these rules
'react-hooks/globals': ERROR,
'react-hooks/purity': ERROR,
'react-hooks/set-state-in-effect': ERROR,
'react-hooks/static-components': ERROR,

any random value generators have been moved out from a component to be determined once at usage time, this most affected stories

any syncing of values in a test have been wrapped in an effect, no change otherwise needed

we incorrectly used some useEffects that should have been useLayoutEffects, they rely on measurements in the dom to set state

we dynamically generated a certain Element based on props as a string, this isn't allowed anymore, so just created the same logic as a component. Most notable in Table RAC

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@snowystinger snowystinger changed the title Update eslint plugin react hooks followup chore: Update eslint plugin react hooks followup Oct 17, 2025
@rspbot
Copy link

rspbot commented Oct 17, 2025

@adobe adobe deleted a comment from rspbot Oct 17, 2025

// Use a random drag type so the items can only be reordered within this list and not dragged elsewhere.
let dragType = React.useMemo(() => `keys-${Math.random().toString(36).slice(2)}`, []);
let dragType = React.useMemo(() => randomDragTypeReorderExample, []);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there's a lot of these, i just moved the random generator out of the component, that should be fine because you couldn't drag between different examples this way either and I don't think we render the same one twice as an example

ref2 = useRef(null);
let internalRef1 = useRef(null);
let internalRef2 = useRef(null);
useEffect(() => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other option i guess would be to exclude the compiler from running on test files, but seems better to always be in good habits

}, [tablistRef, wrapperRef, direction, orientation, setCollapsed, prevTabPositions, setTabPositions]);

useEffect(() => {
useLayoutEffect(() => {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why this was an effect, it's meant to measure the dom, so layout effect should be more correct

}
});

// TODO: Lint doesn't catch these, it thinks we're not in a component render cycle here?
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to make of this yet, but not going to handle it in this series of PRs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda weird that it even catches the other ones, the hook that is being called doesn't create a component like https://react.dev/reference/eslint-plugin-react-hooks/lints/static-components demonstrates but perhaps I'm not understanding quite what React is seeing here. To be safe we could define one for table, tr, td, tbody, and th which should cover it I think

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, went ahead and updated them

I'm mostly worried that the compiler isn't looking through these components, so won't do any optimisations
I'll raise an issue with React at some point about it probably

Copy link
Member

@LFDanLu LFDanLu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other changes look good to me, just one comment

}
});

// TODO: Lint doesn't catch these, it thinks we're not in a component render cycle here?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

kinda weird that it even catches the other ones, the hook that is being called doesn't create a component like https://react.dev/reference/eslint-plugin-react-hooks/lints/static-components demonstrates but perhaps I'm not understanding quite what React is seeing here. To be safe we could define one for table, tr, td, tbody, and th which should cover it I think

@rspbot
Copy link

rspbot commented Oct 19, 2025

@rspbot
Copy link

rspbot commented Oct 19, 2025

LFDanLu
LFDanLu previously approved these changes Oct 20, 2025
if (prevIsLoading !== isLoading && !isLoading) {
setShowLoading(false);
setPrevIsLoading(isLoading);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah this one felt weird to me as well, but https://react.dev/reference/react/useState#storing-information-from-previous-renders claims it is better than setting state in an effect (which the linter will now complain about)

Copy link
Member Author

@snowystinger snowystinger Oct 23, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this one is very annoying, but I'm not sure a better way to do it, we "react" to the change in the prop value, which means we set state in render

we use the showLoading state and count on the new render as a response to the timeout up above, so we need to have some way to "un set it"

had to do it in Combobox as well

}, [leftOffset, popoverContainer]);
let [prevPopoverContainer, setPrevPopoverContainer] = useState<HTMLElement | null>(null);
if (popoverContainer && prevPopoverContainer !== popoverContainer && leftOffset.left === 0) {
let {left} = popoverContainer.getBoundingClientRect();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reading from the DOM during render?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crud, i missed that one, i'll look at that

snowystinger and others added 2 commits October 23, 2025 15:16
…ugin-react-hooks-followup

# Conflicts:
#	packages/react-aria-components/stories/ListBox.stories.tsx
@rspbot
Copy link

rspbot commented Oct 23, 2025

@rspbot
Copy link

rspbot commented Oct 23, 2025

Base automatically changed from update-eslint-plugin-react-hooks to main October 23, 2025 18:49
@snowystinger snowystinger dismissed LFDanLu’s stale review October 23, 2025 18:49

The base branch was changed.

@rspbot
Copy link

rspbot commented Oct 24, 2025

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants