Wednesday, October 2, 2019

useEffect

useEffect

This hook interests people connected to life cycle events. For people used to class component's, useHook is the answer.

From the example below, we see how we can do something like componentDidUpdate & componentDidUnload. The way we use this hook is pass 2 parameters. First parameter is a callback function and second parameter is dependency array/parameter and a very important one.

useEffect(() => {
    console.log('Component loaded');
    return () => {
      console.log('Component unloaded');
    };
  }, []);

There are 3 key functionalities to this second parameter

  • If we don't pass an empty array, this function will be called for every render
  • If we pass an empty array, this function will be called when the component is loaded
  • If we pass one or more parameters to this array, this function will be called, for every change to those parameters
Here's an example of a simple component with this hook. All that it does is prints current time on screen


import React, { useState, useEffect } from "react";
import GitInfo from "./gitinfo";
import RedditSearch from "./reddit";

function App() {
  const [time, setTime] = useState(null);

  useEffect(() => {
    let handle = setInterval(() => {
      setTime(Date);
    }, 1000);

    return () => {
      clearInterval(handle);
    };
  });

  return (
    <>
      <div className="container">
        <h2>React Hooks - useEffect</h2>
        <main role="main" className="container">
          <h1 className="mt-5">Sticky footer with fixed navbar</h1>
          <p className="lead">
            {time}
            <br />
        </main>
        <hr />        
      </div>
      <footer className="footer">
        <div className="container">
          <span className="text-muted">Place sticky footer content here.</span>
        </div>
      </footer>
    </>
  );
}

export default App;



Let's take this a step further to create an event, say a mousemove event.


import React, { useState, useEffect } from "react";
import GitInfo from "./gitinfo";
import RedditSearch from "./reddit";

const initXY = {
  x: null,
  y: null
};
function App() {
  const [time, setTime] = useState(null);
  const [xy, setXY] = useState(initXY);

  useEffect(() => {
    let handle = setInterval(() => {
      setTime(Date);
    }, 1000);

    return () => {
      clearInterval(handle);
    };
  });

  const mouseMoveHandle = e => {
    setXY({
      x: e.clientX,
      y: e.clientY
    });
  };

  useEffect(() => {
    window.addEventListener("mousemove", mouseMoveHandle);
    console.log('Event added back');
    return () => {
      window.removeEventListener("mousemove", mouseMoveHandle);
    };
  }, []);

  const disableMouseMove = (e) => {
    e.preventDefault();
    console.log('Button event invoked');
    window.removeEventListener("mousemove", mouseMoveHandle);
  }

  return (
    <>
      <div className="container">
        <h2>React Hooks - useEffect</h2>
        <main role="main" className="container">
          <h1 className="mt-5">Sticky footer with fixed navbar</h1>
          <p className="lead">
            {time}
            <br />
            Mouse move event<br />
            `x: ${xy.x}, y: ${xy.y}`
          </p>
          <button className="btn" onClick={disableMouseMove}>Disable mouse move</button>
        </main>
        <hr />        
      </div>
      <footer className="footer">
        <div className="container">
          <span className="text-muted">Place sticky footer content here.</span>
        </div>
      </footer>
    </>
  );
}

export default App;



Typical example for useEffect in the contemporary is when a form is loaded, we need to have control focus to be on the right field, for user to start editing. Together with useRef hook we can easily achieve that. Further more, when there are multiple fields involved, we can enable/disable buttons, add validations through useEffect.

In addition to form handling one other place useEffect comes handy is to invoke remote web services



import React, {useEffect, useState} from 'react'
import JSONPretty from 'react-json-pretty';

const GitInfo = () => {

  const [profile, setProfile] = useState(null);
  const getGitProfile = async () => {
    const response = await fetch("https://api.github.com/users/krishnansriram")
    const json = await response.json();
    console.log(json);
    setProfile(json)
  }
  useEffect(() => {
    getGitProfile()
    console.log('Received git profile');
  }, []);
  
  return (
    <div className="container">
      <h4>GIT profile</h4>
      <JSONPretty id="json-pretty" data={profile}></JSONPretty>
    </div>
  )
}

export default GitInfo;

As I stated earlier some of the real-world concepts include enable/disable button based on input values from other fields. In the past we used to write validation functions all over the place and invoke them, just to find out issues and no central place to handle it etc. As you go through more samples from the BLOG, you'll see what I mean. Back to this case, here's a sample

import React, {useState, useRef, useEffect} from "react";
import { useDispatch } from "react-redux";
import uuid from "uuid/v4";

const TodoInput = () => {
  const [name, setName] = useState('');
  const [description, setDescription] = useState('');
  const addButtonRef = useRef();
  const txtName = useRef();
  useEffect(() => {
    txtName.current.focus();
  }, [null])
  
  useEffect(() => {
    console.log('Enable/Disable Add button')
    if(name.length > 5 && description.length > 5) {
      addButtonRef.current.removeAttribute("disabled");
      console.log('Expect button to be enabled, from NOW');
    } else {
      addButtonRef.current.setAttribute("disabled", true);
    }
  }, [name, description])

  const onAdd = (e) => {
    e.preventDefault();
    // Invoke your API here
  }
  return (
    <div>
    <form>
    <div className="form-row">
      <div className="col-3">
        <input type="text" className="form-control" ref={txtName} placeholder="Name" onChange={(e) => setName(e.target.value)} />
      </div>
      <div className="col-7">
        <input type="text" className="form-control" placeholder="Description" onChange={(e) => setDescription(e.target.value)} />
      </div>
      <div className="col">
        <button className="btn btn-primary" ref={addButtonRef} onClick={onAdd}>Add</button>
      </div>
    </div>
  </form>
    </div>
  );
};

export default TodoInput;

We take ref to input components and track them as user types in. Validates is then and then and enable "Add" button. Much simpler than traditional approach.....

We are not restricted with just UI stuff alone. We can build a custom hook to do remote fetch. Say, you need to load profile from GitHub into one of the component/page. Here's how useEffect can help. Later when we look at context and redux examples, you'll see how much this helps's not only at component level, but at app level. With PWA's knocking our door, this functionality can be super helpful, even if it does not make much sense now.

import {useEffect, useState} from 'react';

const useFetch = (url) => {
  const [profile, setProfile] = useState(null);

  const getGitProfile = async (url) => {
    const response = await fetch(url)
    const json = await response.json();
    console.log(json);
    setProfile(json)
  }
  
  
  useEffect(() => {
    getGitProfile(url)
    console.log('Received git profile');
  },[url]);

  return {profile}
}

export default useFetch;

Using above code in our application is lot more easier and cleaner too!

import React from 'react'
import JSONPretty from 'react-json-pretty';
import useFetch from './useFetch';

const GitInfo = () => {
  const fetchProfile = useFetch("https://api.github.com/users/krishnansriram");
  
  return (
    <div className="container">
      <h4>GIT profile</h4>
      <JSONPretty id="json-pretty" data={fetchProfile.profile}></JSONPretty>
    </div>
  )
}

export default GitInfo;

No comments: