Reputation:
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
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
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.
Upvotes: 1