DEV Community

adam klein
adam klein

Posted on

5 ways that will change how you write React hooks

NOTE!

Tips and methods in this post are my own personal preference, and I'm sure many people will disagree.

There is no right or wrong. Every approach has its pros and cons. If you take just one good thing out of the post - I've done my share.

I do not mean to insult anyone who thinks differently. Some of the "bad code" examples used to be code that I've written myself!

If you do think differently - you are more then welcome to comment and change my mind.

Good reading!

1. An effect has no name

Writing several effects in the same component?

const MyComp = () => {
  useEffect(() => {
    ...
  });
  useEffect(() => {
    ...
  });
  useEffect(() => {
    ...
  });
}
Enter fullscreen mode Exit fullscreen mode

I don't want to read your code just to know what they are doing... duh...

Here's a tip for you, use named functions:

const MyComp = () => {
  useEffect(function fetchData() {
    ...
  });
  useEffect(function subscribeToUpdates() {
    ...
  });
  useEffect(function useInterval() {
    ...
  });
}

Enter fullscreen mode Exit fullscreen mode

Much better right?

There's another benefit - you will see the effect name in React dev tools:
Alt Text

Don't be a smarty pants and try to extract to constants, like this:

const MyComp = () => {
  function fetchData() {...}
  function subscribeToUpdates() {...}
  function useInterval() {...}

  useEffect(fetchData);
  useEffect(subscribeToUpdates);
  useEffect(useInterval);
}

Enter fullscreen mode Exit fullscreen mode

Because then you are only fooling the linter, not me!
(Exhaustive deps rules won't work for the function implementations)

2. async functions

Effects don't support async functions (you can't return a promise).
It's so annoying, let's try to solve it:

const MyComp = () => {
  useEffect(() => {(async() => {
    ...
  })();});
}
Enter fullscreen mode Exit fullscreen mode

WTF?! IIFE?! Are we in 2010?!
Try again please:

const MyComp = () => {
  async function fetchData() {...}
  useEffect(() => {
    fetchData();
  });
}
Enter fullscreen mode Exit fullscreen mode

No! You are not listening! (See comment above about exhaustive deps)

OK, I'll give it to you:

const MyComp = () => {
  useEffect(function doSomething() {
    async function doSomethingAsync() {
    }
    doSomethingAsync();
  });
}
Enter fullscreen mode Exit fullscreen mode

Sometimes you just gotta be verbose with them code.

Or, if you insist on taking the function out, take it out completely from the component and pass it the deps:

async function doSomethingAsync(dep1, dep2) {
  ...
}

const MyComp = () => {
  useEffect(function doSomething() {
    doSomethingAsync(dep1, dep2);
  }, [dep1, dep2]);
}
Enter fullscreen mode Exit fullscreen mode

3. Debouncing the hooks way

It's really stupid to implement your own debounce when there is a ton of libraries out there who already have. Right?!

Wrong! 'Cause now we got hooks!!

const MyComp = () => {
  useEffect(function doSomethingDebounced() {
    const timeout = setTimeout(() => {
      doSomethingWith(value);
    }, 500);
    return () => clearTimeout(timeout);
  }, [value]);
}
Enter fullscreen mode Exit fullscreen mode

Ummm.... what?
Yes, that is an implementation of debounce with nothing but effect, timeout and cleanup function. You're smart, think about it.

4. useCallbacks? Nahhh....

You might think that useReducer is better than useState when managing a complex object:

function reducer(state, action) {
  switch(action.type) {
    case 'MOVE_RIGHT':
      return { ...state, left: state.left + action.step };
    case 'MOVE_DOWN':
      return { ...state, top: state.top + action.step };
    default:
      return state;
  }
}
const [position, dispatch] = useReducer(reducer, { left: 0, top: 0 });
Enter fullscreen mode Exit fullscreen mode

But think about it, you will still have to use useCallback for each action dispatch if you want a stable ref:

const moveRight = useCallback((step) => dispatch({ type: 'MOVE_RIGHT', step }), []);
Enter fullscreen mode Exit fullscreen mode

Think about this version instead:

const [position, setPosition] = useState({ left: 0, top: 0 });
const actions = useMemo(() => ({
  moveRight: step => {
    setPosition(state => ({ ...state, left: state.left + step }))
  },
  moveDown: step => {
    setPosition(state => ({ ...state, top: state.top + step }))
  }
}), []);
Enter fullscreen mode Exit fullscreen mode

All actions are memoized!
And no switch case, meaning better debugging experience and TypeScript integration.

5. Use useContext with selectors to bail out of render

You've probably heard many times that it's impossible to bail out of rendering if you're using Context.

Well.....

You're right. I'll give it to you.

But cross your fingers, 'cause selectors are coming to a version near you:

https://github.com/reactjs/rfcs/pull/119

When this is accepted, we would be able to do this:

useContext(MyContext, value => value.someProperty);
Enter fullscreen mode Exit fullscreen mode

Hopefully in the next few months, but who knows, right?!

Hope you learned something new! Tell your friends.

Top comments (8)

Collapse
 
denvash profile image
Dennis Vash • Edited

Good read, named callbacks are a very good intake :)

Collapse
 
monfernape profile image
Usman Khalil

I definitely learned something new today. Thank you for this wonderful piece

Collapse
 
vmuthabuku profile image
vincent muthabuku

Nice one, Changed how I think

Collapse
 
shuzootani profile image
shuzo

Thanks for practical tips that I can do right away!!

Collapse
 
galanggg profile image
Galanggg

thanks for sharing

Collapse
 
fernandosouza profile image
Fernando Souza

Arrow functions are great but the old and good named functions are still in place in terms of "labeling" methods. +1 for this tip.

Some comments may only be visible to logged-in visitors. Sign in to view all comments.