DEV Community

Cover image for A React state hook for computed values
Elad Bezalel
Elad Bezalel

Posted on • Edited on

A React state hook for computed values

Hooks are great!

But I found myself writing a lot of useState with useMemo for having computed fields for every state.

Let's look at an example.

Say I have this component:

const Component = () => {
  const [num, setNum] = useState(0);

  return (
    <div>
      <button onClick={() => setNum(num + 1)}>Inc</button>
      <button onClick={() => setNum(num - 1)}>Dec</button>
      <span>{num}</span>
    </div>
  );
}

And I want to display if the number is even or not and the number multiplied by 10.

So I'll do:

const Component = () => {
  const [num, setNum] = useState(0);
  const isEven = useMemo(() => num % 2 === 0, [num]);
  const multiplied = useMemo(() => num * 10, [num]);

  return (
    <div>
      <button onClick={() => setNum(num + 1)}>Inc</button>
      <button onClick={() => setNum(num - 1)}>Dec</button>
      <span>{num}</span>
      <span>isEven: {isEven}</span>
      <span>by 10: {multiplied}</span>
    </div>
  );
}

This is not bad, but obviously this is rarely the case.
Usually, these mapper functions add up and your state is more complicated than that.
You end up with a lot of map functions and it all gets really messy!

So we can wrap it all with a simple custom hook that gets an initial value and an unlimited list of map functions -

function useMappedState(initialState, ...mapFns) {
  const [state, setState] = useState(initialState);

  const memo = useMemo(() => mapFns.map(mapFn => mapFn(state)), [state]);

  return [state, setState, memo];
}

Typescript

It gets really complicated with Typescript though, how can you get an unlimited number of map functions and get their return type applied for each destructured value?

Of course, you can use function overloads for each added map function, but you end up working for your types. (and I hate working for my types)

Typescript Mapped types to the rescue - lets us iterate over each map function and get back the return type, we can dive into it, but this is for another post.

This is how it looks like

// types:  number       Dispatch...      boolean     string
const [    number   ,    setNumber   , [ isEven , numberName ]] = useMappedState(
  initialValue,
  value => value % 2,
  value => value + 'name'
)

I've just published use-mapped-state, give it a go and let me hear your thoughts! :D

EDIT: As requested, I'll create another post talking more about the encountered problem with typescript, how I solved it and what I learned about it

P.S. Credit for @yoav for the prototype and the name

Top comments (1)

Collapse
 
thomasburleson profile image
Thomas Burleson

Really love to hear your ideas and construction of typescript mapped types