Quick version:
My ultimate goal is to do something like the link below but with an async call to firebase per useEffect
where the list data is composed of firebase object content.
https://codesandbox.io/s/usage-pxfy7
Problem
In the code below useEffect
encapsulates code that pings firebase and gets some data back called "clients". The data is retrieved perfectly.
I then store that data using useState
to two different instances of useState
. The data is stored at clientList
and clientListForRender
.
So far so good.
Now the problem starts.
I have a third instance of useState
that takes a number. I want to set a keypress event to the document so that I can use the up/down arrows to toggle the counter and access each value of the clientListForRender
array.
When I set the eventListener I do not have access to the array (presumably due to the async calls not being in an order that allows for it).
I am not sure how to write my hooks in a way that gives me the result I want.
Thank you.
const clientsRef = firebase.database().ref('clients');
const [clientList,setClientListState] = useState([]);
const [clientListForRender,setClientListStateForRender] = useState([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
useEffect(() => {
function handleKeyPress(event,arr){
console.log(arr)
if(event.key === "ArrowDown"){
updateSelectedIndex((prev)=>{
return prev += 1
});
}
}
clientsRef.on('child_added', snapshot => {
const client = snapshot.val();
client.key = snapshot.key; // __________________________1. get firebase data
setClientListState(function(prev){
setClientListStateForRender(()=>[client,...prev]); //_______2 store data
// document.addEventListener('keydown', handleKeyPress); <---I am not sure where to put this. I have experimented and
// I decided to omit my cluttered "experiments" to protect your eyes
return[client,...prev]
});
});
},[]);
The addEventListener() method attaches an event handler to a document.
The addEventListener() method allows you to add event listeners on any HTML DOM object such as HTML elements, the HTML document, the window object, or other objects that support events, like the xmlHttpRequest object.
Use the document. querySelectorAll() method to select the elements by class. Use the forEach() method to iterate over the collection of elements. Use the addEventListener() method to add an event listener to each element.
bind() method is used for attaching an event handler directly to elements. Handlers are attached to the currently selected elements in the jQuery object, so those elements must exist at the point the call to . bind() occurs. For more flexible event binding, see the discussion of event delegation in .
Binding Event Handler in Render Method: We can bind the handler when it is called in the render method using bind () method. Binding Event Handler using Arrow Function: This is pretty much the same approach as above, however, in this approach we are binding the event handler implicitly.
For earlier versions, the .bind () method is used for attaching an event handler directly to elements. Handlers are attached to the currently selected elements in the jQuery object, so those elements must exist at the point the call to .bind () occurs. For more flexible event binding, see the discussion of event delegation in .on ().
Event handler code can be made to run when an event is triggered by assigning it to the target element's corresponding onevent property, or by registering the handler as a listener for the element using the addEventListener () method. In either case the handler will receive an object that conforms to the Event interface (or a derived interface ).
How to bind an event handler to a class method in Typescript? In ES5, we can very easy bind a event to DOM using the native way: document.addEventListener () , or a jQuery way, $ (element).bind () , $ (element).click () or $ (element).on () , use them according to different situations.
Ok there are few issues with the code you posted:
1) You should definitely not add your keyboard listener in the child_ added listener ( this means that every time the child_added listener is called, you are going to create a new listener, leading to unexpected results and memory leak)
2) You are calling setState in a setState updater function (the callback function you provided for, setClientListState), which is an anti pattern and makes your code hard to follow and understand, and will cause unexpected effects once the component grows. If you want to update a state based on a previous state then use the useEffect callback
3) the useEffect function takes a second parameter, called array of dependencies. When you have provided it with an empty array, it means that you want your effect to run only once, which is problematic because we see that the function depends on clientsRef variable. ( from this actually comes your problem because the keyboard listener was having the old value of your clientsList which is the empty array, and so it was always returning 0, when keys where pressed, i explained more in the code sandbox)
4)You should return a callback function from the useEffect function to clean the effects you created, turning off the listeners you attached (or else you might have memory leaks depending on how much the component gets mounted/unmounted)
ok here is how the code should be to work:
const clientsRef = firebase.database().ref('clients');
const [clientList, setClientListState] = useState([]);
// I don't understand why you wanted another list, so for now i only use on list
// const [clientListForRender,setClientListStateForRender] = useState([]);
const [selectedIndex, updateSelectedIndex] = useState(0);
useEffect(() => {
function handleKeyPress(event, arr) {
if (event.key === 'ArrowDown') {
updateSelectedIndex(prev => {
if (prev >= clientList.length - 1) {
return (prev = 0);
} else {
return prev + 1;
}
});
}
}
clientsRef.on('child_added', snapshot => {
const client = snapshot.val();
client.key = snapshot.key; // __________________________1. get firebase data
setClientListState(function(prev) {
return [client, ...prev];
});
});
document.addEventListener('keydown', handleKeyPress);
// here you should return a callback to clear/clean your effects
return () => {
document.removeEventListener('keydown', handleKeyPress);
clientsRef.off();
};
// Its important to add these here, or else each time your keyboard listener runs it will have the initial value of
// clientsList ([]), and so clientsList.length = 0, and so you will always updateSelectedIndex(0)
}, [clientList, clientsRef]);
//here render based on selected list as you wish
Finally i have set up a working codesandbox that emulated data fetching based on the example you give https://codesandbox.io/s/usage-4sn92, i added some comments there to help explain what i said above.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With