React Advanced Hooks - getfutureproof/fp_guides_wiki GitHub Wiki

So far we have got quite familiar with useState, useEffect and some of the React Router hooks. There are many more at our disposal from various different sources - the core React library, 3rd party libraries and hooks we write ourselves.

Here we will cover just two additional in built hooks which you may well find useful in your upcoming work, and we will also cover how to write your own hooks. At the end of this guide, there is an example of how we can combine these together, creating a custom hook to encapsulate a more complex implementation of useContext.

Check out the official documentation for the hooks that come with the core React library.
As you can see there are more than we cover here and as they say themselves, "Don’t stress about learning them up front."


useRef

When working in React, we like to avoid direct query or manipulation of the DOM so as to make the most of the React flow. So what if we want to do something to a similar effect of a document.querySelector('#select-me')? This is where useRef comes into play. A very popular usage of this is to update scroll position. When starting out with useRef, make sure to remember that it returns an object, not the element itself. In the example below, note the the element is stored in the "current" key of the useRef object.

The most common usage of useRef is for referencing DOM elements, but it can hold any data you like!

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

const NotePad = () => {
    const [ notes, setNotes ] = useState([{ timestamp: Date.now(), body: 'Welcome to your NotePad!'}])
    const [ input, setInput ] = useState("")
    const notesEnd = useRef();

    useEffect(() => {
        notesEnd.current.scrollIntoView({ behavior: "smooth" })
    }, [notes])

    const handleChange = e => setInput(e.target.value)

    const handleSubmit = e => {
        e.preventDefault();
        let newNote = { timestamp: Date.now(), body: input }
        setInput('')
        setNotes(prev => [ ...prev, newNote ])
    }

    const renderNotes = () => notes.map((n, i) => <p key={i}>{n.body}</p>)

    return (
        <section id="notepad">
            <div id="notes-container">
                { renderNotes() }
                <span ref={notesEnd}></span>
            </div>
            <form onSubmit={handleSubmit}>
                <input type="text" value={input} onChange={handleChange}/>
            </form>
        </section>
    )
}

useContext

useContext provides us with a way to access a piece of data from any of our components without relying on trickle-down props. If you're thinking that this sounds a bit like redux then you're on the right track. useContext is a much more limited solution to the same issue but can be extremely effective when use appropriately.

Once a context has been Created, there are two stages of its implementation: Providing the data and Accessing the data.

Creating context

const LanguageContext = React.createContext()

Providing

<LanguageContext value={"en"} />
    <App />
</LanguageContext>

Accessing

import React, { useContext } from 'react';
import { LanguageContext } from '/path/to/context';

const AComponentToTranslate = () => {
    const language = useContext(LanguageContext);

    return <section>Translating content to {language}</section>
}

customHooks

As we know, hooks are just functions so there is no reason we cannot create our own. Aside from within React functional components, they are the only other place we can utilise other hooks. Custom hooks are perfect for abstracting out complex and/or reused functionality.

Custom hooks must follow the hooks naming convention of starting with use

// in customHooks.js
import { useState } from 'react';

export function usePigFlyStatus(theSkyIsGreen){
    const [ pigsCanFly, setPigsCanFly ] = useState();

    if(theSkyIsGreen){
        setPigsCanFly(true)
    }

    return pigsCanFly
}
// in MyFunctionalComponent.js
import React from 'react';
import { usePigFlyStatus } from '../customHooks';

const MyFunctionalComponent = ({ theSkyIsGreen }) => {
    const pigsCanFly = usePigFlyStatus(theSkyIsGreen);

    return <h1>{pigsCanFly ? 'Flying Pigs!' : 'Nothing to see here...'}</h1>
};

Useful Combo Implementation

Consider this implementation of a theme context. Note how it is a little cumbersome, so we have abstracted it to a custom hook.

Creating context and custom hook

// /src/contexts/theme.js
import React, { useState, useContext } from 'react';

const ThemeContext = React.createContext();

export const useThemeContext = () => useContext(ThemeContext);

export function ThemeProvider({ children }){
    const [ darkMode, setDarkMode ] = useState(true);

    const toggleTheme = () => setDarkMode(prev => !prev);

    return (
        <ThemeContext.Provider value={{ darkMode, toggleTheme }} >
            { children }
        </ThemeContext.Provider>
    )
}

Wrapping app in custom Provider

// /src/index.js
import { ThemeProvider } from "./contexts/theme";
import App from './App';

ReactDOM.render(
    <ThemeProvider
        <App />
    </ThemeProvider>,
    document.getElementById('root')
);

Accessing functionality via custom hook

// /src/layout/Header.js
import { useThemeContext } from './contexts/theme';

function Header(){
    const { darkMode, toggleTheme } = useTheme();

    return (
        <div>
            <h1>Welcome</h1>
            <p>Current theme: {darkMode ? "Dark" : "Light"}</p>
            <button onClick={toggleTheme}>Toggle Theme</button>
        </div>
    )
}

futureproof students can see a full auth solution using useContext and custom hooks in the demo repo

⚠️ **GitHub.com Fallback** ⚠️