Sometimes I have to use some native js libaray api, So I may have a component like this:
function App() {
const [state, setState] = useState(0)
useEffect(() => {
const container = document.querySelector('#container')
const h1 = document.createElement('h1')
h1.innerHTML = 'h1h1h1h1h1h1'
container.append(h1)
h1.onclick = () => {
console.log(state)
}
}, [])
return (
<div>
<button onClick={() => setState(state => state + 1)}>{state}</button>
<div id="container"></div>
</div>
)
}
Above is a simple example. I should init the lib after react is mounted, and bind some event handlers. And the problem is coming here: As the above shown, if I use useEffect()
without state
as the item in dependencies array, the value state
in handler of onclick may never change. But if I add state
to dependencies array, the effect function will execute every time once state changed. Above is a easy example, but the initialization of real library may be very expensive, so that way is out of the question.
Now I find 3 ways to reslove this, but none of them satisfy me.
current
every time once state
changed. (A extra variable and effect)ref
. (Some as the first)So is there some resolutions that solve problems and makes code better?
I think you've summarised the options pretty well. There's only one option i'd like to add, which is that you could split your code up into one effect that initializes, and one effect that just changes the onclick. The initialization logic can run just once, and the onclick can run every render:
const [state, setState] = useState(0)
const h1Ref = useRef();
useEffect(() => {
const container = document.querySelector('#container')
const h1 = document.createElement('h1')
h1Ref.current = h1;
// Do expensive initialization logic here
}, [])
useEffect(() => {
// If you don't want to use a ref, you could also have the second effect query the dom to find the h1
h1ref.current.onClick = () => {
console.log(state);
}
}, [state]);
Also, you can simplify your option #1 a bit. You don't need to create a useEffect to change ref.current
, you can just do that in the body of the component:
const [state, setState] = useState(0);
const ref = useRef();
ref.current = state;
useEffect(() => {
const container = document.querySelector('#container');
// ...
h1.onClick = () => {
console.log(ref.current);
}
}, []);