React Hooks - useState and useEffect

React Hooks – useState and useEffect

tl;dr

Demo for react hooks usage – useState and useEffect. As I found out I haven’t write any post about the React Hooks yet.  😅

The first thing I look into React Hooks is the ability to reuse stateful logic (At that time, I haven’t familiar with the concept of Higher order Component yet.) Thus, I’m excited for the React Hooks where I found it can reduce a lot of repeated stateful logic.

Introduction

React Hooks was introduced in version 16.8. It allow you to interact with state in functional component.

#1 useState

@ Example 1

import React, { useState } from 'react';

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

@ Example 2

import React from 'react';

function Example() {
  const [value, setValue] = React.useState(0);
}

[value, setValue] – array destructuring in ES 6.

useState is return two element.

  • First element is the value of a state
  • Second element is the function to update the state
  • Naming convention doesn’t matter, but the order does

** How to swapping element using array destructuring ?

#2 useEffect

useEffect – a combination of componentDidMount, componentDidUpdate and componentWillUnmount.

@ Example

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // synchronous callback
    windows.addEventListener();

    // cleanup, equivalent to componentWillUnmount
    return () => {
      windows.removeEventListener();
    }
  }, [dependencies])
  // dependencies will handle whether to re-run the useEffect or not
  // if dependencies value remain the same. it will skip useEffect
  // otherwise, execute the useEffect
}

@ Example – Execute once

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // Equivalent to componentDidMount
    // Code, execute once on when the component mounted
  }, [])
}

@ Example – Execute every re-render

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // Code, execute on every re-render
  })
}

@ Example – Execute only when dependencies updated

import React, { useEffect } from 'react';

function Example() {
  useEffect(() => {
    // Equivalent to componentDidUpdate
    // Execute if dependencies updated
    // Otherwise, skip useEffect
  }, [dependencies])
}

@ Example – Asynchronous in useEffect

import React, { useEffect } from 'react';

function Example() {
  // You are not allowed to do this, the callback is always synchronous
  useEffect(async () => {
    // Code
  }, [dependencies])
}

// you can try below
function Example() {
  useEffect(() => {
    async fetchSomething() {
      // Code
    }

    fetchSomething();
  })
}

#3 Yet another example

@ Example – Fetching blog posts

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

function MyBlog() {
  const [posts, setPosts] = useState([]);
  const [selectedCategories, setSelectedCategories] = useState(new Set([])); // Selected categories for filter
  const [page, setPage] = useState(0); // blogposts pagination
  const [loading, setLoading] = false;

  useEffect(() => {
    // Declare a function to fetch blogpost from WordPress API
    async fetchPosts(blogPage, blogCategory) {
      try {
        setLoading(true);
        const res = await fetch(
          `https://blog.yikkok.com/wp-json/wp/v2/posts?_embed&page=${blogPage}&categories=${blogCategory}&per_page=5`,
          {
            method: "GET"
          }
        ).then(response => response.json());

        if (res) {
          setPosts([...res]);
          setIsLoading(false);
        }
      } catch (error) {
        // handling error
        setIsLoading(false);
      }
    }
   
   const selectedCategoriesStr = Array.from(selectedCategories).join(",");
   fetchPosts(page, selectedCategoriesStr)
  }, [page, selectedCategories])
  // Because I'm using page and selectedCategories inside useEffect,
  // Thus, I pass the page and selectedCategories as the dependencies,
  // As I want the useEffect to re-run whenever either one updated

  function handleUpdatePage(pageNumber) {
    setPage(pageNumber);
  }

  function handleUpdateSelectedCategories(selected) {
    // A closure
    return () => {
      // Just for demo purpose, it may contain some bugs...
      const cloned = new Set(selectedCategories);

      if (cloned.has(selected)) {
        cloned.delete(selected)
      } else {
        cloned.add(selected)
      }

      setSelectedCategories(cloned);
    }
  }

  return (render UI)
}

#4 Custom React Hooks

Let’s write a custom React Hooks, Using useState and useEffect.

import React, { useState, useEffect } from 'react'

// React Hooks naming convention, always prefix with 'use'
export default function useBlogPosts() {
  const [posts, setPosts] = useState([]);
  const [selectedCategories, setSelectedCategories] = useState([]);
  const [page, setPage] = useState(0);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState("");

  useEffect(() => {
    async fetchPosts(blogPage, blogCategory) {
      try {
        setLoading(true);
        const res = await fetch(
          `https://blog.yikkok.com/wp-json/wp/v2/posts?_embed&page=${blogPage}&categories=${blogCategory}&per_page=5`,
          {
            method: "GET"
          }
        ).then(response => response.json());

        if (res) {
          setPosts([...res]);
          setLoading(false);
        }
      } catch (error) {
        // handling error
        setLoading(false);
        setError(error.message);
      }
    }
   
   const selectedCategoriesStr = Array.from(selectedCategories).join(",");
   fetchPosts(page, selectedCategoriesStr)
  }, [page, selectedCategories]);

  function handleUpdateSelectedCategories(selected) {
    const cloned = new Set(selectedCategories);

    if (cloned.has(selected)) {
      cloned.delete(selected)
    } else {
      cloned.add(selected)
    }

    setSelectedCategories(cloned);
  }

  function handleUpdatePage(pageNumber) {
    setPage(pageNumber);
  }

  return { 
    posts,
    loading,
    error,
    page,
    selectedCategories,
    handleUpdateSelectedCategories,
    handleUpdatePage
  }
}

#5 Use the custom React Hooks

// Refactor MyBlog Component
import React from 'react';
import useBlogPosts from 'src/hooks/useBlogPosts';

function MyBlog() {
  const blog = useBlogPosts();

  // blog.selectedCategories
  // blog.handleUpdatePage
  // blog.handleUpdateSelectedCategories
  // blog.error
  // blog.loading
  // blog.posts
  // blog.page
}

With custom React Hooks, we can separate the stateful logic out of UI component, which make us easier to debug the code without being distracted by the UI code.

Please visit my github commit, how I refactor the source code into custom React Hooks.

Takeaway

useState

  • Access and interact state in functional component

useEffect

  • Synchronous
  • Work similar to componentDidMount, componentDidUpdate and componentWillUnmount

Custom React Hooks – Reuse stateful logic.

Thanks for reading.  😁

Comments

(0 Comments)

Your email address will not be published. Required fields are marked *