Álvaro
Álvaro

Reputation: 2588

Unable to get hook from context provider

I am setting up this custom hook with context provider based on this approach

const mainState = (initial: number | null = null) => {
  const [edit, setEdit] = React.useState<number | null>(initial)

  return {
    edit,
    clear: () => setEdit(null)
  }
}

const MainContext = React.createContext<ReturnType<typeof mainState> | null>(null)

export const useMainContext = () => React.useContext(MainContext)

export function MainProvider({ children }: { children: React.ReactNode }) {
  return (
    <MainContext.Provider value={mainState()}>{children}</MainContext.Provider>
  )
}

Then I am wrapping the whole app in the provider

import React from 'react'
import ReactDom from 'react-dom'
    
import { MainProvider } from './store'
import { App } from './app'
    
ReactDom.render(
  <MainProvider>
    <App />
  </MainProvider>,
  document.getElementById('root')
)

But when I try to get the hook TypeScript errors with

Property 'edit' does not exist on type '{ edit: number | null; clear: () => void; } | null'

import * as React from 'react'
import { useMainContext } from './store'
    
export const App = () => { 
  const { edit } = useMainContext()
  ...
}

Upvotes: 0

Views: 309

Answers (1)

jperl
jperl

Reputation: 5112

As Typescript is telling you, the type of your context can either be null or { edit: number | null; clear: () => void; }

Remember what you initially set it to: const MainContext = React.createContext<ReturnType<typeof mainState> | null>(null).

You can't destructure null so that's why Typescript isn't happy. Either you don't use destructuring:

const context = useMainContext()
context?.edit

or you set the context to a different value with a default value for edit.


I just noticed that mainState was using useState but mainState seems to be neither a custom react hook nor a component. The react docs states that a custom react hook should start with use. And a hook has to be used in either another custom react hook OR a component.

So one way to fix your code:

const useMainState = (initial: number | null = null) => { // this is now a custom react hook
  const [edit, setEdit] = React.useState<number | null>(initial)

  return {
    edit,
    clear: () => setEdit(null)
  }
}

export function MainProvider({ children }: { children: React.ReactNode }) {
  // it's called in a component, Ok
  const mainState = useMainState()
  return (
    <MainContext.Provider value={mainState}>{children}</MainContext.Provider>
  )
}

Now React can keep track of your state. That's why it's also important never to use a hook after a if. The same number of hooks have to be used at all time in a component.

This quite similar to what you did, but you made the call in the prop and even if it worked, it's not that good design-wise.

Upvotes: 1

Related Questions