React Hooks

Hooks are functions that let you “hook into” React state and lifecycle features from function components. Hooks allow the simplicity of functional components with the complexity and flexibility of class components.

If these hooks are not exactly what you need, you can create your own custom hooks using the existing ones to achieve your goals.

These examples are using Typescript for type annotations and clarity.

useContext

useContext is a way to pass global state values around to different components by wrapping components within a provider and then invoking the consumer in the necessary components.

In its most basic form (taken from Dave Ceddia[2]):

import React, { createContext } from 'react';

// Create a Context
const NumberContext = createContext();
// It returns an object with 2 values:
// { Provider, Consumer }

const App = () => {
  // Use the Provider to make a value available to all
  // children and grandchildren
  return (
    <NumberContext.Provider value={42}>
      <div>
        <Display />
      </div>
    </NumberContext.Provider>
  );
};

const Display = () => {
  // Use the Consumer to grab the value from context
  // Notice this component didn't get any props!
  return (
    <NumberContext.Consumer>
      {value => <div>The answer is {value}.</div>}
    </NumberContext.Consumer>
  );
};

However, instead of using the Consumer wrapper, you can use useContext and destructure the variables held within:

import React, { createContext, useContext } from 'react';

const NumberContext = createContext();

const App = () => {
  return (
    <NumberContext.Provider value={42}>
      <div>
        <Display />
      </div>
    </NumberContext.Provider>
  );
};

const Display = () => {
  const { value } => useContext(NumberContext);
  return <div>The answer is {value}.</div>;
};

This becomes more and more useful when multiple contexts are used and you can end up with wrappers over wrappers over wrappers and it gets real unwieldy really quick.

Simplifying a Global Context

If one is using many different contexts over all components, these different contexts can be placed into individual files and imported into a 'main' context file that exports a higher order function which wraps any children in these different contexts.

contexts/ExampleContext.jsx (same thing for contexts/AnotherContext.jsx)

import { createContext } from 'react';
import { Channel } from '../types/types';


export interface ExampleContextInformation {
  name: string;
  age: number;
  setName: Function;
  setAge: Function;
}

// Fill this in with the default values. We will redefine the setters
// once in the component where this is used.
export const ExampleContext = createContext<ExampleContextInformation>({ 
  name: '',
  age: null,
  setName: () => {},
  setAge: () => {},
});

contexts/GlobalContext.jsx

import React from 'react';

import { ExampleContext, ExampleContextInformation } from './ExampleContext';
import { AnotherContext, AnotherContextInformation } from './AnotherContext';

interface GlobalProps { 
  exampleContextObject: ExampleContextInformation, 
  anotherContextObject: AnotherContextInformation,
}

export function GlobalContext(props: any) {
  const {
    exampleContextObject,
    anotherContextObject
  }: GlobalProps = props;

  return (
    <ExampleContext.Provider value={exampleContextObject}>
      <AnotherContext.Provider value={anotherContextObject}>
        {props.children}
      </AnotherContext.Provider>
    </ExampleContext.Provider>
  );
}

App.jsx

import React, { useState } from 'react';

import { GlobalContext } from './contexts/GlobalContext';
import { ExampleContextInformation } from './ExampleContext';
import { AnotherContextInformation } from './AnotherContext';

export default function App() {
  const [name, setName] = useState<string>('');
  const [age, setAge] = useState<number | null>('');
  const [anotherVar, setAnotherVar] = useState<number>(0);

  const exampleContextObject: ExampleContextInformation = {
    name,
    age,
    setName: (name: string) => setName(name),
    setAge: (age: number) => setAge(age),
  };

  const anotherContextObject: AnotherContextInformation = {
    anotherVar,
    setAnotherVar: (anotherVar: number) => setAnotherVar(anotherVar),
  }

  return (
    <GlobalContext
      exampleContextObject={exampleContextObject}
      anotherContextObject={anotherContextObject}
     >
      {/* Components in here have access to contexts via useContext */}
      <ChildComponent />
    </GlobalContext>
  )
}

ChildComponent.jsx

import React, { useContext } from 'react';
import { ExampleContext } from './ExampleContext';
import { AnotherContext } from './AnotherContext';

export default function ChildComponent() {
  const { age, setAge } = useContext(ExampleContext);
  const { anotherVar } = useContext(AnotherContext);

  return (
    <div>
      <p>Your age is {age} and this is {anotherVar}!</p>
      <button type="button" onClick={() => setAge(Date.now())}></button>
    </div>
  )
}

useEffect

useEffect lets you perform side effects on useState changes, effectively replacing componentDidMount and componentDidUpdate. componentDidUnmount is effectively replaced by returning a "cleanup" function in the useEffect callback.

import React, { useEffect } from 'react';

const testComponent = () => {
  ...

  // this will run on every rerender (generally don't want this)
  useEffect(() => {
    // do stuff
  });

  // this will run the function once on mount
  useEffect(() => {
    // do stuff
  }, []);

  // this will run anytime that variable `names` is changed
  useEffect(() => {
    // do stuff
  }, [names]);

    // this will run anytime that variable `names` is changed and will 
    // run the returned "cleanup" function when the component is unmounted
  useEffect(() => {
    // do stuff
    return function cleanup() {
      // cleanup stuff
    }
  }, [names]);

  ...

useState

useState lets you use state within a functional component.

NOTE: useState does not have a callback as second argument, like the normal setState invocation does. Use useEffect to handle side effects and changes on state updates.

import React, { useState } from 'react';

interface Name {
  first: string,
  last: string,
}

const testComponent = () => {  
  // this will create a getter and setter for state variable `names`
  const [counter, setCounter] = useState<number>(0);
  const [names, setNames] = useState<Name[]>([]);

  // sets counter 
  const onClick = () => {
    setCounter(counter + 1);
  };

  const onSubmit = (firstName, lastName) => {
    setNames([
      ...names,
      {
        first: firstName,
        last: lastName
      }
    ]);
  }  

  ...

References

  1. https://reactjs.org/docs/context.html#dynamic-context
  2. https://daveceddia.com/usecontext-hook/

Last modified: 202111250451