0

I'm trying to set up a context provider for my Next.js application, in Typescript.

I've set up a context provider before in React in plain JS, so this is my shot at learning TS.

In the code below, the value={} prop of Context.Provider returns the error:

Property 'tasks' is missing in type '(State | Dispatch<SetStateAction<State>>)[]' but required in type 'State'

import { createContext, useState } from "react";
import { ReactNode } from "react";

interface Task {
    name: string,
    finished: boolean
}

interface State {
    tasks: Task[]
}

const initialState: State = {
    tasks: []
}

export const Context = createContext<State>(initialState)

type stateProps = {
    children: ReactNode
}
const StateContext = ({ children }: stateProps) => {
    const [state, setState] = useState<State>(initialState)

    return (
        <Context.Provider value={[state, setState]}>
            {children}
        </Context.Provider>
    )
}

export default StateContext;

For some reason, it's saying that tasks is missing in something that has the type (State | Dispatch<SetStateAction<State>>)[], I have no clue where.

I've ran into lots of other issues while learning TS, but for some reason I can't figure this one out. Any ideas?

hans
  • 59
  • 4
  • You've said that your context value is the type `State`, which is an object with a key of `tasks`, but then you set the value of your context to an array of `[state, setState]`, which has no `tasks` key on it. Make sure you have your editor set up properly to show typescript errors inline with your code. – Andy Ray Jan 18 '23 at 00:34
  • Hi @AndyRay, please see my reply to Brietsparks. I do have the VScode extension installed, that's where I get the error message from. – hans Jan 18 '23 at 00:46

1 Answers1

1

The value must be the same shape as the type specified in createContext, which is State in this case.

Your useState already holds a State, so just pass it directly to value

const [state, setState] = useState<State>(initialState)

return (
    <Context.Provider value={state}>

If your context needs to expose a setter, then change your State shape to include one

interface State {
    tasks: Task[]
    setTasks: (tasks: Task[]) => void
}

initialState might need a placeholder for that setter in order to satisfy the type checker

const initialState: State = {
    tasks: []
    setTasks: () => {}
}

Then, you can pass a setter into the value like you originally intended

    const [state, setState] = useState<State>(initialState)

    // might need to memoize this
    const value = {
      tasks: state,
      setTasks: (t: Tasks[]) => setState(t)
    }

    return (
        <Context.Provider value={value}>

The alternative to a placeholder for the setter is to make the context optional.

export const Context = createContext<State|undefined>();

Then, initialState and the placeholder setter are unneeded.

However, you will need to check the return value of useContent(Context) because it might be undefined.

You can put this check in a hook

function useStateContext() {
  const value = useContext(Context)
  if (!value) {
    throw new Error('state context has not been set yet')
  }
  return value;
}

And then use the hook in your components

brietsparks
  • 4,776
  • 8
  • 35
  • 69
  • Hi @brietsparks, in that case, how do I make use of the `setState` function within other components? If I only pass `state`? I used `value={[state, setState]}` so that I'm able to do `const [state, setState] = useContext(Context);` in other components. This is how I learned context in plain JS. – hans Jan 18 '23 at 00:44
  • I see your updated answer, thank you for your help. Is this common, to declare a type for the setter? Someone just said to me: `You'd have to change the type of your context to include a setter. However it's going to be difficult to provide a default value for that case, so the common thing to do is to make the context optional... but then you'll need to check if it's provided before using it.` – hans Jan 18 '23 at 00:50
  • @hans see my edits. I explained a way to do optional context – brietsparks Jan 18 '23 at 00:57
  • @hans Optional contexts are IMO a bad design. FYI a better type signature for the setter would be `React.Dispatch>`. That way it can also accept the [functional version](https://reactjs.org/docs/hooks-reference.html#functional-updates) – Phil Jan 18 '23 at 01:08