Skip to content

React Hooks

Date: 2021-12-26

Why React Hooks?

  • small bonus: no need to worry about converting Functional Components to a Class component if you need state

Classes: duplicate code in lifecycle methods

custom hooks too!

componentDidMount() {
    this.updateRepos(this.props.id)
}

componentDidUpdate (prevProps) {
    if (prevProps.id !== this.props.id) {
        this.updateRepos(this.props.id)
    }
}

becomes

useEffect(() => {
    this.updateRepos(id)
}, [id])

Historical Context

uidotdev: Why React Hooks? (history)

  • React.createClass
  • ES6 class
  • now: just functions!

Sharing non-visual logic

  • Higher Order Components (HOC)
    • withRouter
    • injects extra props that's shared between components
  • Render prop
    • <Route path="/home" render={() => <p>Home</p>} />
Custom Hooks

Custom hooks are just regular functions that happen to use other hooks

They can return anything, do anything

Before: HOCs

withRepos(Component)

After: custom hooks

useRepos()

2 Rules of Hooks

  1. Hooks must be used in a React component
  2. Hooks must be declared at the top of the function
    1. must be called in the same order every time
      1. can't be in an if statement
    2. see implementation of useState

useState

  • accepts a function which is a lazy initializer!
    • useState ignores the param afterward
    • for computationally expensive stuff (async, like getFromLocalStorage)
    • so that the expensive function only runs once
const [item, setItem] = useState(() => getFromLocalStorage());

Implementing useState

useEffect

  • dependency array does a === comparison
    • will always re-render if you pass it an array

Implementing useEffect

useReducer

function reducer(state, action) { ... }

const [state, dispatch] = useReducer(reducer, initialValue);

dispatch(action)
  • first prop: the old state
  • second prop: whatever you pass to dispatch
    • usually action
  • doesn't have to follow the Redux style of having actions with a type prop

Implementing useState with useReducer

const [count, setCount] = useState(0);
function countReducer(state, newState) {
    return newState;
}

const [count, setCount] = useReducer(0);
setCount(1);

useReducer vs useState

  • useReducer might be good for more complex logic

useCallback and useMemo

Use cases (https://kentcdodds.com/blog/usememo-and-usecallback)

  1. not have a function re-render every time so that it can be used in a useEffect dep array
  2. avoiding unnecessary re-renders when re-rendering is super expensive (Graphs, Charts, Animations)
  3. useMemo can be passed a function (just like useState) and lazily calculate computationally expensive items
    1. I used it in HierarchySelect where there was derived state that wasn't updated often
import { useCallback } from 'react';

const MyParent = ({ seardchTerm }) => {
  const onItemClick = useCallback(event => {
    ...
  }, [term]);
  return (
    <MyBigList
      searchTerm={searchTerm}
      onItemClick={onItemClick}
    />
  );
}
  • Without useCallback, the onItemClick would be redefined whenever a useState or a parent's state changes
    • which means <MyBigList /> would be re-rendered every time useState or a parent's state is called

what's the run function that we return in useAsync?

  • instead of the user putting their fetch in a useCallback
  • the useEffect in useAsync becomes run which is memoized with useCallback
  • they can put it in a useEffect and run(result)

safeDispatch (safe fetch) and useEffect cleanup

  • Memory leak when unmounting before the fetch finishes
  • it's not a class component so we can't just have a componentWillMount and abort the fetch
  • I have to cancel subscriptions and async tasks in the useEffect cleanup function

How do you know if a component has been unmounted?

React.useLayoutEffect(() => {
    mountedRef.current = true;
    return () => {
        mountedRef.current = false;
    };
}, []);

useContext

useEffect vs useLayoutEffect

  • same API
  • useEffect is run after pixels are painted
  • useLayoutEffect is run before so it will delay painting
    • can update the DOM before it paints

useImperativeHandle

  • ????

React.forwardRef

  • can pass a ref as a second param
    • instead of the first param with all the other regular props

Could you put ref as a prop? in the first param of the component?

  • yeah but that would cause a re-render
  • you might not want to always re-render when the ref that's passed down to the component changes

Do you need to always use useImperativeHandle when using React.forwardRef?

useDebugValue

  • feels like __repr__ in Python for custom hooks

Last update: 2022-09-23