React 16.8+

Hooks are a new addition in React 16.8. They let you use state and other React features without writing a class.

 

You can reuse custom hooks in functional components, which lets you write less repeated code.

Function components

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
const Welcome = (props) => {
  return <h1>Hello, {props.name}</h1>;
}

const Welcome = props => <h1>Hello, {props.name}</h1>

React.useState()

import React, { useState } from 'react';

function Example() {
  // Declare a new state variable, which we'll call "count"
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

A way to create stateful function components

default value

Previous state as an argument

setCount((prevCount) => prevCount + 1)

When using the React.useState() hook's setter function, we can either provide

  • a value, which will be the new set value


     
  • or a function, where the returned value will be the new set value and the first argument is the previous state
setCount(10)

React.useEffect()

The Effect Hook lets you perform side effects in function components.

 

If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.

React.
useEffect()

import React, { useState, useEffect } from "react";

function Example() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("The component has mounted");

    return () => {
      console.log("The component has unmounted");
    };
  }, []);

  useEffect(() => {
    console.log("The component has updated");
  });

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Lifecycle hooks in function components

Dependency array

Detect changes to a certain state variable

import React, { useState, useEffect } from "react";

function Example() {
  const [count, setCount] = useState(0);
  const [cakes, setCakes] = useState(0);

  useEffect(() => {
    console.log(`The count has changed to ${count}`);
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Increment count
      </button>

      <p>You have {cakes} cakes</p>
      <button onClick={() => setCakes(cakes + 1)}>
        Increment cakes
      </button>
    </div>
  );
}

Only runs when count is changed

Fetch external data on mount

import React, { useState, useEffect } from "react";
import axios from "axios";

function Example() {
  const [posts, setPosts] = useState([]);

  useEffect(() => {
    const fetchPosts = async () => {
      const posts = await axios.get(
        "https://jsonplaceholder.typicode.com/posts"
      );
      setPosts(posts.data);
    };
    fetchPosts();
  }, []);

  return (
    <div>
      <pre>{JSON.stringify(posts, null, 2)}</pre>
    </div>
  );
}

Be careful about unwrapped dependencies

import React, { useState, useEffect } from "react";
import axios from "axios";

function Example() {
  const [posts, setPosts] = useState([]);
  
  const fetchPosts = async () => {
    const posts = await axios.get(
      "https://jsonplaceholder.typicode.com/posts"
    );
    setPosts(posts.data);
  };

  useEffect(() => {
    fetchPosts();
  }, [fetchPosts]);

  return (
    <div>
      <pre>{JSON.stringify(posts, null, 2)}</pre>
    </div>
  );
}

triggers an infinite loop

to prevent it, we need to use React.useCallback()

React.useCallback()

function Example() {
  const [value, setValue] = useState('');

  const handleChange = useCallback((event) => {
    const value = customInputHandler(event);
    setValue(value);
  }, [])

  return (
    <CustomInput onChange={handleChange} value={value} />
  );
}

useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed. This is useful when passing callbacks to optimized child components that rely on reference equality to prevent unnecessary renders

Dependency array

useCallback() with useEffect()

function Example() {
  const match = useRouteMatch();
  const { id } = match.params;
  const [post, setPost] = useState(null);

  const handlePostFetch = useCallback(async () => {
    const result = await axios.get(
      `https://jsonplaceholder.typicode.com/posts/${id}`
    );
    setPost(result.data);
  }, [id]); // function gets re-created when id changes

  useEffect(() => {
    handlePostFetch();
  }, [handlePostFetch]); // the action gets called once handlePostFetch is re-created

  if (!post) return <p>Loading post..</p>;

  return (
    <div>
      <h1 className="title">{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

React.createContext() and React.useContext()

Context provides a way to pass data through the component tree without having to pass props down manually at every level.

React.createContext() and React.useContext()

import React, { useState, createContext, useContext } from "react";

const CountContext = createContext(0);

function CountContainer() {
  const [count, setCount] = useState(0);

  return (
    <CountContext.Provider value={count}>
      <CountRenderer />
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </CountContext.Provider>
  );
}

function CountRenderer() {
  const count = useContext(CountContext);

  return <p>You clicked {count} times</p>;
}

Custom hooks

  • They allow us to abstract logic from components (similar to HOCs) and re-use it
  • Outsourcing logic lets us have smaller components with a clear purpose
  • Abstracting early helps prevent duplication
  • It's easier to debug errors with a clear source, rather than inspecting a long component

useEscapeKey() custom hook

const useEscListener = (handler: Function) => {
    const handleEscape = React.useCallback((e: KeyboardEvent) => {
        const { key, keyCode } = e;
        
        if (key === 'Escape' || keyCode === 27) handler();
    }, [handler]);

    React.useEffect(() => {
        document.addEventListener('keydown', handleEscape, false);

        return () => {
            document.removeEventListener('keydown', handleEscape, false);
        }
    }, [handleEscape]);
};

useEscListener.tsx

const Example = () => {
    useEscListener(() => console.log('The Escape key has been pressed'));

    return (...)
};

Custom hook + Context

import React, { useContext } from 'react';

import UserContext from '../context/UserContext';

/**
 * A reusable hook to handle fetching user from the stored parent provider
 */
const useGetUser = () => {
    const user = useContext(UserContext);

    return user;
};

export default useGetUser;
import React from 'react';

import useGetUser from '../hooks/useGetUser';

export const Header = () => {
    const user = useGetUser();
    
    return (
    	<p>{user.id}</p>
    )
}
import React from 'react';

import { UserProvider } from '../context/UserContext';
import { UserProfileService } from '../helpers/UserProfileHelper';

const App = () => {
    const [user, setUser] = React.useState(null);

    React.useEffect(() => {
        UserProfileService.getInstance().GetUserProfile().then(user => {
            setUser(user);
        })
    }, []);

    return (
      <UserProvider value={user}>
      	{children}
      </UserProvider>
    )
};

Re-render basics

A component re-render is triggered by its parent's re-render, a change of state, props or data of a context it's subscribed to. 

We can alter the re-rendering behavior by memoizing the values we pass down - be it using a context or props.

React.useMemo()

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

useMemo will only recompute the memoized value when one of the dependencies has changed. This optimization helps to avoid expensive calculations on every render.

useMemo() in a component

const ExampleContainer = () => {
  const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    
  return (
    <div>
      <h2>Some heading</h2>
      <p>Some description</p>
      <ExampleChildRenderer value={memoizedValue} />
    </div>
  )
}

Imagine we have an ExampleContainer with some expensive calculating logic. We also have an ExampleChildRenderer component, that's relying on this calculated data.

If we wrap the data in a useMemo (and provide the dependencies), we will minimize the number of times ExampleChildRenderer will try to re-render. Other frameworks could call this value computed.

React.memo()

React.memo is a higher order component.

If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.

https://reactjs.org/docs/react-api.html#reactmemo

It's the equivalent of a PureComponent.

React 16.8+

By Dana Janoskova