Correct way to type nullable state when using React's useState hook

ReactjsTypescriptReact Hooks

Reactjs Problem Overview


I am having trouble figuring out how to type useState function since it returns a tuple. In essence, I have to provide null as initial value for email i.e. lets assume I can't use empty string here.

I then have setEmail function to update this state value, which takes in email as string.

ideally I would like to type my useState so it expects email to be either string or null if possible. At the moment it inherits it as only null

import * as React from "react";

const { useState } = React;

function Example() {
  const [state, setState] = useState({ email: null, password: null });

  function setEmail(email: string) {
    setState(prevState => ({ ...prevState, email }))
  }

  return <p>{state.email}</p>
}

Following error is returned for setEmail function since string in function argument is not valid type for null specified in useState()

[ts]
Argument of type '(prevState: { email: null; password: null; }) => { email: string; password: null; }' is not assignable to parameter of type 'SetStateAction<{ email: null; password: null; }>'.
  Type '(prevState: { email: null; password: null; }) => { email: string; password: null; }' is not assignable to type '(prevState: { email: null; password: null; }) => { email: null; password: null; }'.
    Type '{ email: string; password: null; }' is not assignable to type '{ email: null; password: null; }'.
      Types of property 'email' are incompatible.
        Type 'string' is not assignable to type 'null'. [2345]
(parameter) prevState: {
    email: null;
    password: null;
}

Reactjs Solutions


Solution 1 - Reactjs

Currently, the TypeScript compiler thinks the type of email and password are null (and no other value). You can resolve this by providing an explicit type parameter to the useState call so that the types of email and password are known to be string or null.

const { useState } = React;

function Example() {
  const [state, setState] = useState<{email: null | string, password: null | string}>({ email: null, password: null });

  function setEmail(email: string) {
    setState(prevState => ({ ...prevState, email }))
  }

  return <p>{state.email}</p>
}

Solution 2 - Reactjs

This is addressed in a few spots already:

https://dev.to/busypeoples/notes-on-typescript-react-hooks-28j2

https://codewithstyle.info/Using-React-useState-hook-with-TypeScript/

TLDR: pass a type argument to setState when you have an empty initial state

eg:

const [email, setEmail] = useState<string>();

Solution 3 - Reactjs

you can use TS mapped types to improve readability and prefer undefined over null values

const { useState } = React;

function Example() {
  const [state, setState] = useState<Partial<{email: string, password: string}>>();

  function setEmail(email: string) {
    setState(prevState => ({ ...prevState, email }))
  }

  return <p>{state.email | ""}</p>
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionIljaView Question on Stackoverflow
Solution 1 - ReactjsNurbol AlpysbayevView Answer on Stackoverflow
Solution 2 - ReactjsSean O'LearyView Answer on Stackoverflow
Solution 3 - ReactjsAdrien GardouView Answer on Stackoverflow