Writing cleaner state in React and React Native

Ever since hooks got introduced in React, it made it a lot more easier to handle
composition in react components and also helped the developers of react handle
the component context a lot better. Also, as consumers of the library, we could
finally avoid having to write this.methodName = this.methodName.bind(this)
which was a redundant part of the code to which a few developers ended up
writing their own wrappers around the component context.

But that's old news, why bring it up now?

Well, as developers there's always some of us who just go ahead follow the
standard as is even when it makes maintenance hard and in case of hooks, people
seem to just ignore the actual reason for their existence all together.

If you witnessed the talk that was given during the release of hooks, this post
might be not bring anything new to your knowledge. If you haven't seen the talk

  1. You should.
  2. I'm serious, go watch it!

For the rebels, who are still here reading this, here's a gist of how hooks are
to be used.

Context Scope and hook instances

If you've not seen how hooks are implemented then to be put simply, the hook
will get access to the component it's nested inside and has no context of it's
own, which then gives you ability to write custom functions that can contain
hook logic and now you have your own custom hook.

Eg: I can write something like this

import { useEffect, useState } from "react";

function useTimer() {
  const [timer, setTimer] = useState(1);

  useEffect(() => {
    const id = setInterval(() => {
      setTimer(timer + 1);
    }, 1000);

    return () => clearInterval(id);
  }, [timer, setTimer]);

  return {
    timer,
  };
}

export default function App() {
  const { timer } = useTimer();

  return <>{timer}</>;
}

And that gives me a simple timer, though the point is that now I can use this
timer not just in this component but any component I wish to have a timer
in.

The advantages of doing this

This gives us smaller Component code to deal with while debugging.

What does any of that have to do with state!?

Oh yeah, the original topic was about state... Now the other part of having
hooks is the sheer quantity that people spam the component code with it and
obviously the most used one is useState.

As mentioned above, one way is to segregate it to a separate custom hook but if
you have like 10-20 useState because you are using a form and for some weird
reason don't have formik setup in you codebase then you custom hook will also
get hard to browse through.

And, that's where I really miss the old setState from the days of class
components and there's been various attempts at libraries that recreate the
setState as a hook and I also created one which we'll get to soon but the
solution is basically letting the state clone itself and modify just the fields
that were modified, not that hard right?

You can do something like the following

const [userDetails, setUserDetails] = useState({
  name: "",
  age: 0,
  email: "",
});

// in some handler
setUserDetails({ ...userDetails, name: "Reaper" });

And that works (mostly) but also adds that additional ...userDetails everytime
you want to update state. I say it works mostly cause these objects come with
the same limitations that any JS Object has, the cloning is shallow and nested
states will loose a certain set of data unless cloned properly and that's where
it's easier to just use library's that make it easier for you to work with this.

I'm going to use mine as an example but you can find more such on NPM.

import { useSetState } from "@barelyhuman/set-state-hook";
import { useEffect } from "react";

function useCustomHook() {
  const [state, setState] = useSetState({
    nested: {
      a: 1,
    },
  });

  useEffect(() => {
    /* 
      setState({
        nested: {
          a: state.nested.a + 1
        }
      });
    // or 
    */

    setState((prevState, draftState) => {
      draftState.nested.a = prevState.nested.a + 1;
      return draftState;
    });
  }, []);

  return { state };
}

export default function App() {
  const { state } = useCustomHook();
  return <div className="App">{state.nested.a}</div>;
}

and I can use it like I would with the default class styled setState but if
you go through it carefully, I actually mutated the original draftState and
that's because @barelyhuman/set-state-hook actually create's a clone for you
so you can mutate the clone and when you return it still creates a state update
without actually mutating the older state.

Summary

make it easier on your brain to read the code you write.