Wrapping React.useState with TypeScript

Wrapping React.useState with TypeScript

[ad_1]

I made a useDarkMode hook that appears like this:

kind DarkModeState = 'darkish' | 'mild'
kind SetDarkModeState = React.Dispatch<React.SetStateAction<DarkModeState>>

serve as useDarkMode() {
  const preferDarkQuery = '(prefers-color-scheme: darkish)'
  const [mode, setMode] = React.useState<DarkModeState>(() => {
    const lsVal = window.localStorage.getItem('colorMode')
    if (lsVal) {
      go back lsVal === 'darkish' ? 'darkish' : 'mild'
    } else {
      go back window.matchMedia(preferDarkQuery).suits ? 'darkish' : 'mild'
    }
  })

  React.useEffect(() => {
    const mediaQuery = window.matchMedia(preferDarkQuery)
    const handleChange = () => {
      setMode(mediaQuery.suits ? 'darkish' : 'mild')
    }
    mediaQuery.addEventListener('exchange', handleChange)
    go back () => mediaQuery.removeEventListener('exchange', handleChange)
  }, [])

  React.useEffect(() => {
    window.localStorage.setItem('colorMode', mode)
  }, [mode])

  // we are doing it this manner as an alternative of as an impact so we most effective
  // set the localStorage price in the event that they explicitly exchange the default
  go back [mode, setMode] as const
}

Then it’s used like this:

serve as App() {
  const [mode, setMode] = useDarkMode()
  go back (
    <>
      {/* ... */}
      <House mode={mode} setMode={setMode} />
      {/* ... */}
      <Web page mode={mode} setMode={setMode} />
      {/* ... */}
    </>
  )
}

serve as House({
  mode,
  setMode,
}: {
  mode: DarkModeState
  setMode: SetDarkModeState
}) {
  go back (
    <>
      {/* ... */}
      <Navigation mode={mode} setMode={setMode} />
      {/* ... */}
    </>
  )
}

serve as Web page({
  mode,
  setMode,
}: {
  mode: DarkModeState
  setMode: SetDarkModeState
}) {
  go back (
    <>
      {/* ... */}
      <Navigation mode={mode} setMode={setMode} />
      {/* ... */}
    </>
  )
}

serve as Navigation({
  mode,
  setMode,
}: {
  mode: DarkModeState
  setMode: SetDarkModeState
}) {
  go back (
    <>
      {/* ... */}
      <button onClick={() => setMode(mode === 'mild' ? 'darkish' : 'mild')}>
        {mode === 'mild' ? <RiMoonClearLine /> : <RiSunLine />}
      </button>
      {/* ... */}
    </>
  )
}

This works nice, and powers the “darkish mode” beef up for all of the
Epic React workshop apps (as an example
React Basics).

I need to name out a couple of issues concerning the hook itself that made issues paintings neatly
from a TypeScript viewpoint. First, let’s filter all of the further stuff and
simply have a look at the essential bits. We will even filter the TypeScript and upload it
iteratively:

serve as useDarkMode() {
  const [mode, setMode] = React.useState(() => {
    // ...
    go back 'mild'
  })

  // ...

  go back [mode, setMode]
}

serve as App() {
  const [mode, setMode] = useDarkMode()
  go back (
    <button onClick={() => setMode(mode === 'mild' ? 'darkish' : 'mild')}>
      Toggle from {mode}
    </button>
  )
}

From the get-go, we have now were given an error when calling setMode:

This expression isn't callable.
  Now not all constituents of kind 'string | React.Dispatch<SetStateAction<string>>' are callable.
    Sort 'string' has no name signatures.(2349)

You’ll learn each and every addition of indentation as “as a result of”, so let’s learn that
once more:

This expression isn’t callable. As a result of now not all constituents of kind
'string | React.Dispatch<SetStateAction<string>>' are callable. As a result of
kind 'string' has no name signatures.(2349)

The “expression” it is relating to is the decision to setMode, so it is announcing that
setMode is not callable as a result of it may be both
React.Dispatch<SetStateAction<string>> (which is a callable serve as) or
string (which isn’t callable).

For us studying the code we all know that setMode is a callable serve as, so the
query is: why is the setMode kind each a serve as and a string?

Let me rewrite one thing and we’re going to see if the explanation jumps out at you:

const array = useDarkMode()
const mode = array[0]
const setMode = array[1]

The array on this case has the next kind:

Array<string | React.Dispatch<React.SetStateAction<string>>>

So the array that is being returned from useDarkMode is an Array with
parts which might be both a string or a React.Dispatch kind. So far as
TypeScript is anxious, it has no concept that the primary component of the array is
the string and the second one component is the serve as. All it is aware of evidently is that
the array has parts of the ones two sorts. So once we pull any values out of
this array, the ones values must be probably the most two sorts.

However React’s useState hook manages to verify once we extract values out of it.
Let’s take a snappy have a look at their kind definition for useState:

serve as useState<S>(
  initialState: S | (() => S),
): [S, Dispatch<SetStateAction<S>>]

Ah, so they have got a go back kind this is an array with particular sorts. So moderately
than an array of parts that may be one in every of two sorts, it is explicitly an array
with two parts the place the primary is the kind of state and the second one is a
Dispatch SetStateAction for that form of state.

So we want to inform TypeScript that we intend to verify our array values do not
ever exchange. There are a couple of tactics to try this, lets set the go back kind for
our serve as:

serve as useDarkMode(): [string, React.Dispatch<React.SetStateAction<string>>] {
  // ...
  go back [mode, setMode]
}

Or lets make a particular kind for a variable:

serve as useDarkMode() {
  // ...
  const returnValue: [string, React.Dispatch<React.SetStateAction<string>>] = [
    mode,
    setMode,
  ]
  go back returnValue
}

Or, even higher, TypeScript has this capacity integrated. As a result of TypeScript
already is aware of the kinds in our array, so we will simply inform TypeScript: “the sort
for this price is continuing” so we will forged our price as a const:

serve as useDarkMode() {
  // ...
  go back [mode, setMode] as const
}

And that makes the entirety glad with no need to spend a ton of time typing out
our sorts 😉

And we will take it a step additional as a result of with our Darkish Mode capability, the
string can also be both darkish or mild so we will do higher than TypeScript’s
inference and go the conceivable values explicitly:

serve as useDarkMode() {
  const [mode, setMode] = React.useState<'darkish' | 'mild'>(() => {
    // ...
    go back 'mild'
  })

  // ...

  go back [mode, setMode] as const
}

This will likely assist us once we name setMode to verify we now not most effective name it with a
string, however the proper form of string. I additionally created kind aliases for this and
the dispatch serve as to make the prop sorts more uncomplicated as I go those values
round my app.

Hope that used to be fascinating and useful to you! Revel in 🎉

[ad_2]

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back To Top
0
Would love your thoughts, please comment.x
()
x