Last time we talked about React Hooks, we stuck to the main two that was needed to know to replace the functionality you were accustomed to in Class components. With that understanding, there is only one question that should be left on your mind. What else you got?

useContext

Starting things off, we have useContext. In React there is a concept about Context. To understand Context, think of passing props through many levels of nested components. Now let’s say these props for the most part are static values that never change, just more so static values that need to be passed down.

For most, the obvious route would be just to pass down props layer after layer. However, Context makes that much easier. Here is an example:

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

In this above example, we see our static values set for theme and it being used in a child component thanks to useContext.

useCallback

This hook is a bit more straightforward. Think of useCallback as an optimization tool when wiring up a method that needs to be passed down or used in a way where it will be present in different levels of your application’s scope and you don’t want a change to happen unless one of it’s dependencies changes.

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b],
);

useMemo

Similar to useCallback, this hook sets up a memorized value that only recalculates based on the dependencies based or when memory needs to be cleared so the value is recalculated. While this vale stores a memorized value, it is a waste of time to use this hook with a static value that would better served assigned to a const declaration.

A much more useful approach would be to memorize a value that goes through some functional computation to be derived. Below you’ll find an example:

const computeValue = (a, b) => {
  let returned;
  ............
  ............
  return retunred;
};
const memoizedValue = useMemo(() => 
  computeValue(a, b), [a, b]);

useRef

In using this hook, you’ll have a reference to a particular DOM node. This reference stays with this value throughout the component lifecycle, and doesn’t get lost through the re-rendering process.

function TextInputWithFocusButton() {
  const inputEl = useRef(null);
  const onButtonClick = () => {
    // `current` points to the mounted text input element
    inputEl.current.focus();
  };
  return (
    <>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </>
  );
}

useReducer

For those that have used Redux in their applications, this really isn’t anything new to learn. This hook serves a pretty similar purpose to useState. The difference comes into play with how you need to compute the stored value, and the type of value you’re storing.

For instance, say you have a counter that can have a call to increase and decrease. In using useState to do that you’d have to write out a method in your setState call with using the previous state value to compute that. With this hook useReducer tho, that’s not the case.

Like in Redux, the reducer logic keeps a current record of its current value and takes another param as an action to decide what needs to be done to change the value. As you’ll see below:

const initialState = {count: 0};

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <>
      Count: {state.count}
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

In Closing

Between both parts of this lesson we were able to cover a lot of great things on hooks! The most important thing to keep in mind here is that while these are all great options to use in your codebase, they are just that, options. In application architecture there is no “technically” right or wrong way, it’s more so about what is the best approach for your current situation.

Take that in mind when building out the ideas you have in your head, and happy coding!