GAZAR

Principal Engineer | Mentor

Best Tutorial for 11 React Hooks

Best Tutorial for 11 React Hooks

Haven’t written anything for a while, I have decided to review all the react hooks we have now and how to use them in this article.

First, let's review React hooks and talk about what they are and why we need them at all.

What is React Hook?

If you want to write components as functions, you need to use hooks to have a lifecycle of any components. This feature was introduced a few months ago and since then, it’s been a trend and the most used thing in react components.

1. useState

const [state, setState] = useState(initialState);

As you might remember, we used to set state like this in class functions

class MyComponent extends React.Component {
   this.state = {sample:'gazar'}
   handleClick = () => {
      this.setState({ sample: 'some another name' })
   }
   render(){
     return (<div />)
   }
}

Which it was using Class lifecycles. but now if we want to use functions, we can use useState.

const MyComponent = () => {
   const [sample, setSample] = useState('gazar');
   handleClick = () => {
      setSample('some another name')
   }
   return (<div />)
}

It is easier than you thought of probably and it can easily be used in the JSX.

2. useEffect

useEffect(didUpdate);

Another hook that is really useful and necessary to learn. useEffectcan be used instead of componentDidMount, componentDidUpdate . Let's review a few of these scenarios:

Scenario 1: Before we used to do this:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  componentDidMount() {
    document.title = `You clicked ${this.state.count} times`;
  }
  componentDidUpdate() {
    document.title = `You clicked ${this.state.count} times`;
  }
  render() {
    return (
      <div>
        <p>You clicked {this.state.count} times</p>
        <button onClick={() => this.setState({ count: this.state.count + 1 })}>
          Click me
        </button>
      </div>
    );
  }
}

but now we can do this instead:

const MyComponent = () => {
  const [count, setCount] = useState(0);
  useEffect(() => {
    document.title = `You clicked ${count} times`;
  });
  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Scenario 2: Another use case useEffect is to use it conditionally: something like this that we were doing before:

class MyComponent extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      numberOfChanges: 0
    };
  }
  componentDidUpdate(prevProps) {
    if (prevProps.name != this.props.name ) {
       this.setState({
          numberOfChanges: this.state.numberOfChanges+1
       })
    }
  }
  render() {
    return (
      <div />
    );
  }
}

We can do this much easier in useEffect :

const MyComponent = ({ name }) => {
  const [numberOfChanges, setNumberOfChanges] = useState(0);
  useEffect(() => {
    setNumberOfChanges(numberOfChanges+1)
  }, [name]);
  return (
    <div />
  );
}

Scenario 3: What if we want to have async/await like before:

class MyComponent extends React.Component {
  async componentDidMount() {
    await callApi()
  }
  render() {
    return (
      <div />
    );
  }
}

we can do this instead:

const MyComponent = () => {
  useEffect(async () => {
    await callApi()
  });
  return (
    <div />
  );
}

The last scenario that might happen is componentWillUnMount

class MyComponent extends React.Component {
  componentDidMount() {
     window.addEventListener('mousemove', () => {})
  }
  componentWillUnmount() {
     window.removeEventListener('mousemove', () => {})
  }
  render() {
    return (
      <div />
    );
  }
}

It can be like this in hooks:

const MyComponent = () => {
  useEffect(() => {
     window.addEventListener('mousemove', () => {});
     return () => {
       window.removeEventListener('mousemove', () => {})
     }
  }, [])

  return (
    <div />
  );
}

3. useContext

const value = useContext(MyContext);

Another useful feature of react which was released I think l while back, it was Context APIs,

const ThemeContext = React.createContext('light');
class App extends React.Component {
  render() {
    return (
      <ThemeContext.Provider value="dark">
        <Toolbar />
      </ThemeContext.Provider>
    );
  }
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

class ThemedButton extends React.Component {
  static contextType = ThemeContext;
  render() {
    return <Button theme={this.context} />;
  }
}

But now with useContext we can do:

ThemedButton = () => {
  const theme = useContext(ThemeContext);
  return (
    <Button theme={theme} />;
  );
}

If you do not know what React Context API is, I demand you to learn it now :)

4. userReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

Hope you are familiar with Redux, so read this from React website:

(be mindful that this is different from Redux hooks, same concept but this is implemented in react and it’s not an additional library)

“useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.” (link)

So this example helps you to understand how it works:

const initialState = {count: 0};
const reducer = (state, action) => {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1}
    case 'decrement':
      return {count: state.count - 1}
  }
}

const Counter = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

5. useCallback

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

Fancy and useful for performance is to use useCallbackl to prevent unnecessarily defining functions.

Imagine you have a scenario like this:

const increment = (() => {
  setCount(count + 1)
})
const decrement = (() => {
  setCount(count - 1)
})
const incrementOtherCounter = (() => {
  setOtherCounter(otherCounter + 1)
})

As you see, you have three functions and each time one of them is called, React will render the whole function and it defines all of these functions again or as you might say re-instantiates.

To prevent this from happening and having a better performance, you can do this:

const increment = useCallback(() => {
  setCount(count + 1)
}, [count])
const decrement = useCallback(() => {
  setCount(count - 1)
}, [count])
const incrementOtherCounter = useCallback(() => {
  setOtherCounter(otherCounter + 1)
}, [otherCounter])

Now but having a condition, you have defined that if the condition is not true, do not create a new instance anymore.

6. useMemo

const memoizedValue = useMemo(() => handleExpsValue(a, b), [a, b]);

Do you have a calculation that is happening in each render? like filtering something or just sorting an array?

we used to do these things in componentDidMount but now you can use useMemo and that solves the problem.

const memoizedCallback = useMemo(doSomething(a, b),[a, b]);

Note:

useCallback(fn, deps) is equivalent to useMemo(() => fn, deps).

so the idea is:

useMemo focuses on avoiding heavy calculations.

useCallback focuses on different thing: it fixes performance issue when inline event handlers like onClick={() => { doSomething(...); } makes PureComponent child re-render (since function expression there is referentially different each time)

7. useRef

const refContainer = useRef(initialValue);

Having a ref of an element on the page to control it in react, we used to do it differently but with using this hook you can simplify things out. like:

const inputEl = useRef(null);
const onButtonClick = () => {
   inputEl.current.focus();
};

return (
   <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  ); 

So simple 😆

9. useLayoutEffect

Too fancy 😎, this is same as useEffect , however, it will only run after all the DOM mutations, it means that it will be exactly like componentDidMount and componentDidUpdate . but why is it fancy?

According to react documentation it’s better to start off with useEffect and if you had a scenario that needed this and it could only work with this just use it.

Personally, all the scenarios for me were fine with useEffect

10.useDebugValue

useDebugValue(value)

Debugging things out, it’s even easier with this hook, it labels your hook in your React DevTool.

function useTestFunction() {
   const [testVar, setTestVar] = useState(true);
   useDebugValue(testVar ? 'Online' : 'Offline');
   return testVar;
}
useTestFunction();

and in debug tools it shows it like this:

1_J1TsZufW29n1KqoI_vrQtg.webp

Useful if you are thinking you might need to do more debugging in your app.

11. useImperativeHandle

Hopefully, you never get to use this, however, there are scenarios that needs this hook,

FancyInput = (props, ref) => {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));

  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

Normal way it might be passing a function to the child component and calling it there to get the ref of the child in the parent and do something with it. (what?)

const Child = ({getRef}) => {
   return (
   <>
      <input ref={getRef} />
   </>
   )
}

const ParentComponent = () => {
   const handleGetRef = (childRef) => {
      console.log('childRef',childRef)
   }
   return (
      <Child getRef={handleGetRef} />
   )
}

Too much hassle. so what is the easier way? of course, using this useImperativeHandle which it would be like this:

const ParentComponent = () => {
   const inputRefInChild = React.useRef(null);
   useEffect(() => {
      console.log('inputRefInChild',inputRefInChild)
   }, []);

   return (
      <Child ref={inputRefInChild} onBlur={onBlur} />
   )
}

const Child = forwardRef((props, parentRef) => {
   const inputRef = useRef();
   useImperativeHandle(parentRef, () => inputRef.current);

   return (
      <>
         <input ref={inputRef} {...props} />
      </>
   );
})

I hope this article was useful to you, to me certainly was ;)


Comments