-
-
Notifications
You must be signed in to change notification settings - Fork 445
fix: Conditionally rendering fields in <StrictMode> #576
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
fix: Conditionally rendering fields in <StrictMode> #576
Conversation
… strict mode, but it might create others
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
I'm not sure this is right. I thought, and I could be wrong, that a function passed to Let me check and confirm this is the case. Either way, I wouldn't want to go down this path as I think there are other options available to us. Edit: Yup, the function App() {
const [count, setCount] = useState(() => ({
count: 1,
}));
const [_, rerender] = useReducer(() => ({}), {});
return (
<div>
<button onClick={() => rerender()}>{count.count}</button>
<button onClick={() => setCount((v) => ({ count: v.count++ }))}>
{count.count}
</button>
</div>
);
} |
Hmmm, I think the docs do say that the initializer function of a
And if we modify an external state in the initializer function, then we won't have a clean start for the second render. Check this (horrendous and client-only) modification of your example (see on StackBlitz): import { useState, useReducer } from 'react';
import reactLogo from './assets/react.svg';
import viteLogo from '/vite.svg';
import './App.css';
function App() {
const [count, setCount] = useState(() => {
if (!window.executeCount) window.executeCount = 0;
window.executeCount++;
return {
count: window.executeCount,
};
});
const [_, rerender] = useReducer(() => ({}), {});
return (
<div>
<button onClick={() => rerender()}>{count.count}</button>
<button onClick={() => setCount((v) => ({ count: v.count++ }))}>
{count.count}
</button>
</div>
);
}
export default App; For me, the counter shows Is it really not possible that we have an impure initializer function for a
|
OMG you're right. I never knew about this 🫠🫠 Let me see if I can't figure out a different solution to this problem knowing this now |
OK so things that don't seem to work include:
I like your solution the best, but let's add it into: #606 instead of this PR |
Addresses #571.
TL;DR
It seems like the bug can be tracked to
useField
creating duplicate instances of the field (FieldApi
) in strict mode and not cleaning them up properly. This PR at this point is just a proof of concept for a solution.Details of the bug
Basically, the
useField
hook runs twice in strict mode, and creates a new instance ofFieldApi
every time it runs (source):I think that problem is that the cleanup function only runs once, because it's wrapped in a
useIsomorphicEffectOnce
hook source:Honestly, there's a lot of things happening here, so I'm not a 100% percent sure I read the situation correctly. 😆
My solution and questions
I thought the best way to approach this issue is preventing a duplicate
FieldInstance
from being created, by checking if there's an instance already in the initialization function of theuseState
hook inuseField
. My question is: how dangerous is it? 🤣 In what cases would the field have multiple instances (in general, not just at this point)? Also, I'd love to understand a bit more abouttanstack/store
, especially if it's important here (a quick overview would be enough). I'm happy to get guidance on Discord, if a (or rather the) maintainer has time for it.