user17610593
user17610593

Reputation:

How to call createContext with React/TypeScript

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>
  );
};

Upvotes: 1

Views: 2686

Answers (2)

Reza
Reza

Reputation: 465

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 />
  )
}

Upvotes: 0

Alex Wayne
Alex Wayne

Reputation: 186984

{ 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()

Upvotes: 1

Related Questions