1

Hi :) I'm running into an issue when trying to set up context with createContext. I need/want to access state and dispatch in my child components.

I'd also like to know: is the way I'm trying to pass down state and dispatch the right way? Or is there another (better) way to do this?

The error I get: No value exists in scope for the shorthand property 'state'. Either declare one or provide an initializer.

Here's (part of) my App.tsx:

interface IState {
  hasStarted: boolean;
  chosenAmount: string;
  chosenCategory: string;
  chosenDifficulty: string;
}

const initialState: IState = {
  hasStarted: false,
  chosenAmount: "",
  chosenCategory: "",
  chosenDifficulty: "",
};

// ... 

interface IStateContext {
  state: IState
  dispatch: React.Dispatch<Action>
}

export const StateContext = createContext<IStateContext>({state, dispatch}); // error here

export const App: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <StateContext.Provider value={{state, dispatch}}>
      {state.hasStarted ? <QuestionsPage /> : <StartPage dispatch={dispatch} />}
    </StateContext.Provider>
  );
};
3
  • Remove the arguments from the createContext function.You don't need them. export const StateContext = createContext(); Commented Apr 24, 2022 at 22:10
  • @WiseCode You'd think, but no. Commented Apr 25, 2022 at 1:23
  • I found this blog post helpful: kentcdodds.com/blog/how-to-use-react-context-effectively Commented Aug 1, 2022 at 17:04

2 Answers 2

1

{ a } is shorthand for: { a: a }

If a exists, then this works fine:

const a = 123
const obj = { a }

But if it does not then you get this error:

const obj = { a }
// No value exists in scope for the shorthand property 'a'.
//   Either declare one or provide an initializer.(18004)

Which means that this line:

export const StateContext = createContext<IStateContext>({state, dispatch});

calls the createContext function with an argument that is an object with two values named state and dispatch.

The problem is that when this function called those values don't exist yet. That's what this error means:

No value exists in scope for the shorthand property 'state'.

Create an object via { state } expects there to be a value named state that will be the value of the state property of the object.


To fix it you need to provide the createContext() with a proper default object. That means it must fully confirm to the IStateContext type.

Something like:

const initialState: IState = {
  hasStarted: false,
  chosenAmount: "",
  chosenCategory: "",
  chosenDifficulty: "",
};

export const StateContext = createContext<IStateContext>({
    state: initialState,
    dispatch: (action: Action) => undefined
);

I, personally, prefer this little hook that will throw an error if a context is used without a provider as a parent. This means you don't need to define a default value at all, and for large and complex contexts, that is really nice.

useStrictContext()

Sign up to request clarification or add additional context in comments.

2 Comments

Hi @alex! Thank you for your reply. I'm probably implementing your fix the wrong way, but I get this error: Type '{ setHasStarted: () => never; }' is not assignable to type 'Dispatch<Action>'. Object literal may only specify known properties, and 'setHasStarted' does not exist in type 'Dispatch<Action>'. See: imgur.com/a/4smwBOS. Any idea? :)
Apparently the type for Dispatch is type Dispatch<A> = (value: A) => void which is just a function. So dispatch: (action: Action) => undefined should work. See my updated answer.
0

A complete working sample 8.2022 (react 18):

import React, { createContext, Reducer, useContext, useReducer } from 'react';

interface IState {
    testNum: number
}

interface IPayload {
    something: string;
}

interface IAction {
    type: string;
    payload: Partial<IPayload>;
}

export interface IStateContext {
    context: Partial<IState>
    dispatch: React.Dispatch<IAction>
}

const AppContext = createContext<IStateContext>({
    context: {},
    dispatch: () => undefined
});

const AppReducer = function (state: IState, action: IAction): IState {
    switch (action.type) {
        case 'TEST':
            return { ...state, testNum: state.testNum + 1 }
        default:
            return state
    }
}

const AppProvider = ({ children }: { children: any }) => {
    let initState: IState = { testNum: 10 }
    const [context, dispatch] = useReducer<Reducer<IState, IAction>>(AppReducer, initState)

    return (
        <AppContext.Provider value={{ context, dispatch }}>
            {children}
        </AppContext.Provider>
    )
}

const C1 = () => {
    const { context, dispatch } = useContext(AppContext)
    return <>
        <button onClick={() => dispatch({ type: 'TEST', payload: {} })}>Clicke To Increment</button>
    </>
}

const C2 = () => {
    const { context } = useContext(AppContext)
    return <div>Value is : {context.testNum}</div>
}

function SinglePageSample() {
    return (
        <AppProvider>
            <C1 />
            <C2 />
        </AppProvider>
    )
}

export default function App() {
  return (
    <SinglePageSample />
  )
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.