Reputation:
Given the example below
#app div {
padding: 10px;
border: 1px solid;
margin: 10px 0;
}
div:focus {
background: red;
}
div:focus:before {
content: "focused";
display: block;
}
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<script type="text/babel">
let { useState, useRef, Fragment } = React;
let App = () => {
let [, forceUpdate] = useState();
let [num, setNum] = useState(0);
let history = useRef('null');
let keyPressHandler = () => {
history.current = num;
// just to force an update
forceUpdate(Date.now());
};
return (
<Fragment>
<button onClick={()=>{setNum(Date.now())}}>update state</button>
<div tabindex="0" onKeyPress={keyPressHandler}>this div has a keypress listener. <br/>for now whatever key you press it will save the state in a ref, that changes on the button click. <br/>Click the button to change the state then focus this div then press any key to save the state in the ref</div>
<div>State : {num}</div>
<div>History: {history.current}</div>
</Fragment>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
Now i'm going to move the key press listener to the window, should do the same thing.
#app div {
padding: 10px;
border: 1px solid;
margin: 10px 0;
}
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState, useRef, Fragment } = React;
let App = () => {
let [, forceUpdate] = useState();
let [num, setNum] = useState(0);
let history = useRef('null');
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
}, []);
let keyPressHandler = () => {
console.log('called keyPressHandler')
history.current = num;
// just to force an update
forceUpdate(Date.now());
};
return (
<Fragment>
<button onClick={()=>{setNum(Date.now())}}>update state</button>
<div>State : {num}</div>
<div>History: {history.current}</div>
</Fragment>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
But it doesn't num
(state) is always the initial value.
Upvotes: 0
Views: 388
Reputation: 2713
The reason is that when the window rerender only accesses the val
variable in the first render, it doesn't have access to the new val
in the subsequent render.
state hooks also have a callback, in which the current state is passed in.
In this case, using a callback to read the latest state value and to ensure that you have the latest state value before add it will solve the problem.
example:
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState } = React;
let App = () => {
let [val, setVal] = useState('initial Value')
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
// document.addEventListener("keypress", keyPressHandler);
}, []);
let keyPressHandler = ({ key }) => {
console.log(val)
setVal(val => val + "-" + key); /*<- change this line*/
};
return (
<div tabindex="0" >
<h1>{val}</h1>
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
UPDATED
As for the question asked by Zohir Salak in the comment. I will recommend reading this blog by Dan Abramov. link
EDIT
As the question got updated
the best way to do that will be using the a state to store the history value
if you want to use useref
then the way I can think of is
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState, useRef, Fragment } = React;
let App = () => {
let [,ForceUpdate] = useState();
let [keyUpdate, setKeyUpdate] = useState(0);
let [num, setNum] = useState(0);
let history = useRef('null');
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
}, []);
let keyPressHandler = () => {
console.log('called keyPressHandler');
setKeyUpdate(Date.now());
};
useEffect(() => {history.current = num; ForceUpdate(Date.now()) }, [keyUpdate]);
return (
<Fragment>
<button onClick={()=>{setNum(Date.now())}}>update state</button>
<div>State : {num}</div>
<div >History: {history.current}</div>
</Fragment>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
Upvotes: 1
Reputation: 4273
I think you have to register listener in each update. Alternative you can store the state in a service or in a variable or using useRef hook.
Demo
<div id="app"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/babel.min.js"></script>
<script type="text/babel">
let { useEffect, useState } = React;
let App = () => {
let [val, setVal] = useState('initial Value')
useEffect(() => {
window.addEventListener("keypress", keyPressHandler);
// document.addEventListener("keypress", keyPressHandler);
return () => {
window.removeEventListener("keypress", keyPressHandler);
}
});
let keyPressHandler = ({ key }) => {
console.log(val)
setVal(val + "-" + key);
};
return (
<div tabindex="0" >
<h1>{val}</h1>
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
</script>
Upvotes: 0