SJ logo

React Hooks 101

24.04.20205 Min Read — In React JS

useState

To add some local state

import React, { useState } from 'react';
import { someExpensiveComputation } from './someExpensiveComputation';

const BadgeCount = (props) => {
  const [count, setCount] = useState(0);
  // To lazy load initial state
  const [title, setTitle] = useState(() => {
    const initialState = someExpensiveComputation(props);
    return initialState;
  });

  return (
    <div>
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  );
};

export default BadgeCount;

 

useRef

To give you same ref object on every render, which can hold a mutable value in its .current property. Mutating the .current property doesn’t cause a re-render.

const InputWithFocusButton = () => {
  const inputField = useRef(null);
  // `current` points to the mounted text input element
  const onClick = () => inputField.current.focus();

  return (
    <>
      <input ref={inputField} type="text" />
      <button onClick={onClick}>Focus text input</button>
    </>
  );
};

 

useEffect

Similar to componentDidMountcomponentDidUpdate and componentWillUnmount combined lifecycles. It fires after the browser has painted (i.e. after layout and paint).

  • Without second argument, it runs first on mount and then on every re-render. Similar to componentDidMount and componentDidUpdate.
  • With empty array, it only runs once. Similar to componentDidMount.
  • With second argument with any value inside ([variable1]),it run on mount and when variable changes
  • Any functions run in useEffect must be included in dependencies list
  • Resolve exhaustive-deps
const SomeContent = () => {
  const [count, setCount] = useState(0);
  const [inView, setInView] = useState(false);
  const scrollHandler = () => console.log('scroll');

  useEffect(() => {
    if (isInView) {
      setInView(true);
    }
  }); // Run on mount and then on every re-render

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, []); // Run only once on mount and unmount

  useEffect(() => {
    document.title = `You clicked ${count} times`;
  }, [count]); // Only re-run the effect if count changes

  // Cleaning up an effect
  useEffect(() => {
    window.addEventListener("scroll", scrollHandler);
    return () => {
      // Clean up the event listener
      window.removeEventListener("scroll", scrollHandler);
    };
  });

  const fetchProduct = useCallback(() => {
    // ... Does something with productId ...
  }, [productId]); // All useCallback dependencies are specified

  useEffect(() => {
    fetchProduct();
  }, [fetchProduct]); // All useEffect dependencies must be specified

  return inView ? <div>Some content</div> : <div>...</div>;
};

 

useLayoutEffect

Similar to useEffect (same as componentDidMount and componentDidUpdate) but it fires synchronously after all DOM mutations and before the browser has had a chance to "paint" those changes. Use useEffect when possible to avoid blocking visual updates.

useContext

To read the context and subscribe to its changes in the provider component.

  • access context via {Context}.Consumer. Only accessible within jsx return code
import React from 'react';
import { StatusBarContext } from './StatusBarContext';

const App = () => {
  return (
    <StatusBarContext.Provider value={"loading"}>
      <Home />
      <StatusBar/>
    </StatusBarContext.Provider>
  );
};

// Consume context
const Home = () => {
  return (
    <StatusBarContext.Consumer>
      {(status) =>
        status === 'loading' ? <div>loading</div> : <div>Content</div>
      }
    </StatusBarContext.Consumer>
  );
};
import { StatusBarContext } from "./StatusBarContext";

const StatusBar = () => {
  const status = useContext(StatusBarContext);
  return (
    <p>
      {status === "loading" ? "loading" : "content"}
    </p>
  );
};
  • access context via useContext. Prefer this way as it can use context outside the render block
import React from 'react';
import { StatusBarContext } from './StatusBarContext';

const App = () => {
  return (
    <StatusBarContext.Provider value={"loading"}>
      <StatusBar/>
    </StatusBarContext.Provider>
  );
};
// src/StatusBarContext.js
import React from "react";

const StatusBarContext = React.createContext("loading");
export default StatusBarContext
import { StatusBarContext } from "./StatusBarContext";

const StatusBar = () => {
  const status = useContext(StatusBarContext);
  return (
    <p>
      {status === "loading" ? "loading" : "content"}
    </p>
  );
};

 

useReducer

Use when the next state depends on the previous one or complex state transitions. There are 2 ways to initialize useReducer state:

  • pass the initial state as a second argument
const initialState = { count: 0, Total: 20 };

const counterReducer = (state, action) => {
  switch (action.type) {
    case "increment":
      return { count: state.count++, total: state.total++ };
    case "decrement":
      return { count: state.count--, total: state.total-- };
    case "reset":
      return init(action.payload);
    default:
      throw new Error();
  }
};

const Counter = ({ initialCount }) => {
  const [state, dispatch] = useReducer(counterReducer, initialState);

  return (
    <>
      <p>Total: {state.total}</p>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
    </>
  );
};
  • pass an init function as the third argument. The initial state will be set to init(initialArg).
const initializeState = (width) => ({ width });
const initialState = { width: 15 };
const lineReducer = (state, action) => {
  switch (action) {
    case 'add':
      return { width: state.width + 15 };
    case 'reduce':
      return { width: state.width * 0.9 };
    default:
      throw new Error('action not found');
  }
};

const Line = () => {
  const [state, dispatch] = useReducer(
    lineReducer,
    initialState.width,
    initializeState
  );
  return (
    <>
      <div style={{ background: 'teal', height: '30px', width: state.width }} />
      <div style={{ marginTop: '3rem' }}>
        <button onClick={() => dispatch('add')}>Increase length</button>
        <button onClick={() => dispatch('reduce')}>Decrease length</button>
      </div>
    </>
  );
};

 

useCallback

Returns a memoized callback. It is equivalent to useMemo(() => fn, deps)

const memoizedCallback = useCallback(() => someFunction(a, b), [a, b]);

 

useMemo

Returns a memoized value. It can be used to memoize an expensive re-render of a child.

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
const child1 = useMemo(() => <Child1 a={a} />, [a]);
© 2020 by Silicon Jungles. All rights reserved.
Last build: 15.05.2020