Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bind event handler to document & have access to firebase api data via useEffect

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]
      });

   });


},[]);
like image 574
William Avatar asked Sep 03 '19 04:09

William


People also ask

Which method attaches an event handler to the document?

The addEventListener() method attaches an event handler to a document.

Can you add event listener to 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.

How do I add an event listener to an entire document?

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.

How do you event bind an event handler?

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 .

How do I bind an event handler to a render method?

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.

How do I bind an event to an element?

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 ().

How do I run an event from an event handler?

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?

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.


Video Answer


1 Answers

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.

like image 145
ehab Avatar answered Oct 13 '22 19:10

ehab