0

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/babel-standalone@6.15.0/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&nbsp;&nbsp;&nbsp;&nbsp;: {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/babel-standalone@6.15.0/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&nbsp;&nbsp;&nbsp;&nbsp;: {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.

Rainbow
  • 6,772
  • 3
  • 11
  • 28
  • Possible duplicate of [Listen to keypress for document in reactjs](https://stackoverflow.com/questions/29069639/listen-to-keypress-for-document-in-reactjs) – Easwar May 02 '19 at 15:34
  • Just tried `keyDown` same problem, i don't think it's a problem with which event – Rainbow May 02 '19 at 15:41
  • what are you trying to prove here man if you are not using sate simply use `history.current = history.current + '-' + key` – saurabh May 03 '19 at 17:49
  • @saurabh i have two states and `history.current` is null by default when one of the states changes i store the other one in `history.current` – Rainbow May 03 '19 at 17:53
  • that's exactly my question is why do you want to do that if you are using `useref` then you can simply use ` history.current = history.current + '-' + key` and for the state you can directly use val in the jsx – saurabh May 03 '19 at 18:14
  • all of these doesn't align with the question you initially asked, please ask a new question. A separate from this one stating what exactly you want to ask. – saurabh May 03 '19 at 18:15
  • I don't want to append to the history, i want to access the new state and assign it to the history – Rainbow May 03 '19 at 18:29
  • you can do that using `useEffect()` and also refer this for more understanding [https://stackoverflow.com/questions/54867616/console-log-the-state-after-using-usestate-doesnt-return-the-current-value](https://stackoverflow.com/questions/54867616/console-log-the-state-after-using-usestate-doesnt-return-the-current-value) – saurabh May 03 '19 at 19:07
  • @saurabh updated the question should be much more clear what i'm trying to do. – Rainbow May 03 '19 at 19:18

2 Answers2

1

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/babel-standalone@6.15.0/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/babel-standalone@6.15.0/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&nbsp;&nbsp;&nbsp;&nbsp;: {num}</div>
    <div >History: {history.current}</div>
  </Fragment>
  ); 
}; 

ReactDOM.render(<App />, document.querySelector("#app"));
</script>
saurabh
  • 2,553
  • 2
  • 21
  • 28
  • This may solve the problem but i'm looking for a much thorough answer that explains why this is happening, because as you know in a class component i don't have to do any of this. – Rainbow May 02 '19 at 16:14
  • well the question is `react can't access state from document/window keypress ` and also both of the demo used doesn't use classes – saurabh May 02 '19 at 16:32
  • Your solution only works if i want to update the state, but in some cases i just want to access the new state and do something with it without updating the state – Rainbow May 03 '19 at 15:27
  • I didn't get that, can you give an example? – saurabh May 03 '19 at 16:15
  • Having a second state defies the purpose, now there will be a rerender on every key stroke – Rainbow May 04 '19 at 00:21
0

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/babel-standalone@6.15.0/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>
Luca
  • 4,223
  • 1
  • 21
  • 24
  • that sounds heavy don't you think ? i mean i can't just use a class component and i wouldn't have to register the listener on every update – Rainbow May 02 '19 at 15:52
  • It depends. You can also save your status outside the component in a service or in a variable. For example let AppState = {}. – Luca May 02 '19 at 15:54
  • that defies the purpose of using state – Rainbow May 02 '19 at 15:55
  • Performance should not be your first problem. If you have performance issues you can use services, or variables as transient state. – Luca May 02 '19 at 15:59