React Recipes

Table of Contents

Recipes for building React applications.

How to Count Renders

// define a Logger component that logs how many times it has been called
function Logger() {
  const numRenders = React.useRef(0)
  ++numRenders.current
  console.log(`You have rendered me ${numRenders.current} times.`)
  return null
}

// use the Logger component in your component
function MyComponent() {
    return {
        <Logger />
        <p>Hello, World</p>
    }
}

Using useEffect to console.log updates to state variables

useEffect(() => {
    console.table(todos)
}, [todos])

A RESTful / CRUD Custom Hook

First define a service using Axios:

// TodoService.js
import axios from 'axios'

const API = axios.create({
    baseURL: '/api/todos'
})

const TodoService = {
    reset: () => API.delete('/reset'),
    get: () => API.get('/'),
    put: todo => API.put(`/${todo.id}`, todo),
    post: todo => API.post('/', todo),
    delete: id => API.delete(`/${id}`)
}

export default TodoService

Then define a generic custom hook:

  • generic in that it works for any RESTful endpoint
  • uses Dependency Injection for doing CRUD operations via a Service
// use-crud.js
import { useState, useEffect } from 'react'
import toastr from '../toastr'
import 'toastr/build/toastr.min.css'

function useCrud(service, initialValue, initialLoading) {
    const [items, setItems] = useState(initialValue)
    const [loading, setLoading] = useState(initialLoading)

    // initial load of items
    useEffect(() => {
        ;(async () => {
            try {
                setLoading(true)
                const response = await service.get()
                setTimeout(() => {
                    setLoading(false)
                    setItems(response.data)
                }, 800)
            } catch (error) {
                toastr.error(error)
            }
        })()
    }, [])

    async function create(item) {
        try {
            const response = await service.post(item)
            setItems([...items, response.data])
        } catch (error) {
            toastr.error(error)
        }
    }

    async function destroy(id) {
        try {
            // Filter all items except the one to be removed
            const remaining = items.filter(item => item.id !== id)
            await service.delete(id)
            setItems(remaining)
        } catch (error) {
            toastr.error(error)
        }
    }

    async function destroyMany(filter) {
        const keepers = items.filter(item => !filter(item))
        const losers = items.filter(item => filter(item))
        const promises = losers.map(item => service.delete(item.id))
        Promise.all(promises)
            .then(responses => {
                setItems(keepers)
            })
            .catch(error => {
                console.log('HERE')
                toastr.error(error)
            })
    }

    async function update(updatedItem) {
        try {
            const response = await service.put(updatedItem)
            const updatedItemFromServer = response.data
            const newItems = items.map(item =>
                item.id !== updatedItemFromServer.id ? item : updatedItemFromServer
            )
            setItems(newItems)
        } catch (error) {
            toastr.error(error)
        }
    }

    function length() {
        return items ? items.length : 0
    }

    function find(id) {
        return items.find(item => item.id === id)
    }

    function filter(fn) {
        return items.filter(fn)
    }

    function reduce(fn, initialValue) {
        return items.reduce(fn, initialValue)
    }

    return {
        loading,
        items,
        setItems,
        create,
        destroy,
        destroyMany,
        update,
        length,
        find,
        filter,
        reduce
    }
}

export default useCrud

Usage:

// TodoApp.jsx
import useCrud from '../../hooks/use-crud'

const TodoApp = () => {
    const todos = useCrud(TodoService, [])

    function onUpdateTitle(id, title) {
        const foundTodo = todos.find(id)
        const updatedTodo = {
            ...foundTodo,
            title
        }
        todos.update(updatedTodo)
    }

    function onToggleCompleted(id) {
        const foundTodo = todos.find(id)
        const updatedTodo = {
            ...foundTodo,
            completed: !foundTodo.completed
        }
        todos.update(updatedTodo)
    }

    function onDeleteCompleted() {
        todos.destroyMany(todo => todo.completed)
    }
}

A useInterval Custom Hook

This code was taken from an article by Dan Abramov.

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

function useInterval(callback, delay) {
    const savedCallback = useRef()

    // Remember the latest callback.
    useEffect(() => {
        savedCallback.current = callback
    }, [callback])

    // Set up the interval.
    useEffect(() => {
        function tick() {
            savedCallback.current()
        }
        if (delay !== null) {
            let timer = setInterval(tick, delay)
            return () => clearInterval(timer)
        }
    }, [delay])
}

export default useInterval

usage:

useInterval(() => {
    // Your custom logic here
    setCount(count + 1)
}, 1000)
Dr. Mike Hopper avatar
Dr. Mike Hopper
Software Architect, Engineer, and Instructor