Nicklas Pouey-Winger
Nicklas Pouey-Winger

Reputation: 3023

Is there such a thing as a "correct" way of defining state with React hooks and Typescript?

I've been working with React for a while, and yesterday I got my feet wet with hooks in a Typescript based project. Before refactoring, the class had a state like this:

interface INavItemProps {
  route: IRoute;
}

interface INavItemState {
  toggleStateOpen: boolean
}

class NavItem extends Component<INavItemProps, INavItemState> {
  constructor() {
    this.state = { toggleStateOpen: false };
  }

  public handleClick = (element: React.MouseEvent<HTMLElement>) => {
    const toggleState = !this.state.toggleStateOpen;
    this.setState({ toggleStateOpen: toggleState });
  };

  ...
}

Now, when refactoring to a functional component, I started out with this

interface INavItemProps {
  route: IRoute;
}

const NavItem: React.FunctionComponent<INavItemProps> = props => {
  const [toggleState, setToggleState] = useState<boolean>(false);
  const { route } = props;

  const handleClick = (element: React.MouseEvent<HTMLElement>) => {
    const newState = !toggleState;
    setToggleState(newState);
  };

  ...
}

But then I also tested this:

interface INavItemProps {
  route: IRoute;
}

interface INavItemState {
  toggleStateOpen: boolean
}

const NavItem: React.FunctionComponent<INavItemProps> = props => {
  const [state, setToggleState] = useState<INavItemState>({toggleStateOpen: false});
  const { route } = props;

  const handleClick = (element: React.MouseEvent<HTMLElement>) => {
    const newState = !state.toggleStateOpen;
    setToggleState({toggleStateOpen: newState});
  };
  ...
}

Is there such a thing as a correct way of defining the state in cases like this? Or should I simply just call more hooks for each slice of the state?

Upvotes: 4

Views: 146

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281666

useState hook allows for you to define any type of state like an Object, Array, Number, String, Boolean etc. All you need to know is that hooks updater doesn't merge the state on its own unline setState, so if you are maintain an array or an object and you pass in only the value to be updated to the updater, it would essentially result in your other states getting lost.

More often than not it might be best to use multiple hooks instead of using an object with one useState hook or if you want you can write your own custom hook that merges the values like

const useMergerHook = (init) => {
   const [state, setState] = useState(init);
   const updater = (newState) => {
       if (Array.isArray(init)) {
          setState(prv => ([
             ...prv,
             ...newState
          ]))
       } else if(typeof init === 'object' && init !== null) {
          setState(prv => ({
            ...prv,
            ...newState
          }))
       } else {
          setState(newState);
       }

   }
   return [state, updater];
}

Or if the state/state updates need to be more complex and the handler need to be passed down to component, I would recommend using useReducer hook since you have have multiple logic to update state and can make use of complex states like nested objects and write logic for the update selectively

Upvotes: 5

Related Questions