pwd > ninjaPixel/v2/man-drawer/handy-react-bits

React bits

Published

A reference to save myself from looking these up in the official docs...

useTransition

Sometimes, updating a state value can cause really expensive re-renders (e.g. you type a search term into an input box and this filters the rows in a large, complicated table).

Wrap state updates with startTransition (see example) if they are low priority. Other state updates in your app that are regular priority (e.g. updating the text value in an input) will not have to wait for this re-render to complete.

const [isPending, startTransition] = useTransition();
const [items, filteredItems] = useState(0);

function onSearchChanged() {
    startTransition(() => {
        const newItems = filterNewItems(searchTerm)

        // changing items will cause an expensive re-render
        // which would block all the other stuff on the page from
        // re-rendering
        setItems(newItems);
    })
}

// ...
return //...

Portals

Portals enable you to render to a DOM element that is outside of your React tree. Needing this is rare but is useful for stuff like creating full-screen modals. More details in the proper docs.

Context

useContext to avoid prop-drilling. A neat trick is to use a useState variable as the item that the context holds. This way you have a getter and a setter for the context. e.g.

const SearchContext = React.createContext(["", (str) => str]);

const App: FC = () => {
    const searchTermState = useState("");

    return (
        <SearchContext.Provider value={searchTermState}>
            <div>
                <input
                    value={searchTermState[0]} // getter
                    onChange={(e) => {
                        const value = e.target.value;
                        // setter
                        searchTermState[1](value);
                    }}
                />
                <ItemList/>
            </div>
        </SearchContext.Provider>
    );
};

function ItemList(): React.Element {
    const [search] = useContext(SearchContext);
    // do stuff with the search variable

    return (
        <div>
            foo bar
        </div>
    );
}

Render function

When you want the ‘wrapping’ component to pass some props to your actual component. It’s basically a way of creating a Higher Order Component.

function Moon(props) {
    const phase = "waxing gibous"
    return (
        <div>
            {props.render({phase})}
        </div>
    )
}

function App() {
    return (
        <Moon
            render={(phase) => {
                <span>The phase is {phase}</span>
            }}
        />
    )
}

useMemo

Incredibly useful function to save unnecessary CPU cycles (see working example on code sandbox).

export default function App() {
    const [counter, setCounter] = useState(0);

    /* 
      Bad, each render (each time the button is clicked)
      will cause this function to be called
    */
    const magicNumber = expensiveOperation();

    /*
      Good, as long as the dependencies don't change (and
      we don't have anything in the dependency array) then this function
      is not called.
    */
    const magicNumber2 = useMemo(() => expensiveOperation(), []);

    return (
        <button
            onClick={() => {
                setCounter(counter + 1);
            }}
        >
            Click me!
        </button>
    );
}

Update

I'd always assumed that this function keeps a cache of the values it calculates, but a colleague recently educated me to the fact that useMemo only holds on to the most recent calculation – its cache has a max size of 1!

FYI: If you want to memoize a more substantial set of results, lodash's memoize function is unbounded.

useCallback

Very similar to useMemo, but used when you need to make sure that a function is stable between renders. For example, if you are passing a function to a child component, you don’t want it changing under you as that would cause a re-render of the child component (i.e. a change of props would be detected) . See a working example in this code sandbox.

export default function App() {
    const [counter1, setCounter1] = useState(0);
    const [counter2, setCounter2] = useState(0);

    // bad
    const clickHandler1 = () => {
        setCounter1(counter1 + 1);
    };

    // good
    const clickHandler2 = useCallback(() => {
        setCounter2(counter2 + 1);
    }, [counter2]);

    return (
        <>
            <SpecialButton
                name="Naive"
                count={counter1}
                onClick={clickHandler1}
            />
            <SpecialButton
                name="useCallback"
                count={counter2}
                onClick={clickHandler2}
            />
        </>
    );
}

useLayoutEffect

useEffect runs after React has rendered your component. Occasionally you need to make sure that an effect runs * before* a render (and if you need something to happen before the initial render, you should use this hook (equivalent to componentWillMount in class-based land)).# React Patterns