Reputation: 590
I can't figure out what the type error is in this code
import React from 'react';
interface IMenu {
items: {
title: string,
active: boolean,
label: string
}[]
}
type Action =
| { type: 'SET_ACTIVE', label: string }
const initialState: IMenu = {
items: [
{ title: 'Home', active: false, label: 'home' },
{ title: 'Customer', active: false, label: 'customer' },
{ title: 'Employee', active: false, label: 'employee' },
{ title: 'Report', active: false, label: 'report' },
]
}
const reducer = (state: IMenu = initialState, action: Action) => {
switch (action.type) {
case 'SET_ACTIVE': {
const label = action.label;
const items = state.items.map((item) => {
if (item.label === label) {
return { ...item, active: true };
}
return { ...item, active: false }
})
return { items }
}
default:
throw new Error();
}
};
export const MenuContext = React.createContext(initialState);
export const MenuConsumer = MenuContext.Consumer;
export function MenuProvider(props: any) {
const [state, dispatch] = React.useReducer(reducer, initialState)
const value = { state, dispatch };
console.log(value)
return (
<MenuContext.Provider value={value}>
{props.children}
</MenuContext.Provider>
)
}
The error I receive is this
/Volumes/Tarang External/react-context-typescript/src/context/index.tsx
TypeScript error in /Volumes/Tarang External/react-context-typescript/src/context/index.tsx(49,27):
Property 'items' is missing in type '{ state: { items: { active: boolean; title: string; label: string; }[]; }; dispatch: Dispatch<{ type: "SET_ACTIVE"; label: string; }>; }' but required in type 'IMenu'. TS2741
47 | console.log(value)
48 | return (
> 49 | <MenuContext.Provider value={value}>
| ^
50 | {props.children}
51 | </MenuContext.Prov
Can someone help point out what exactly I'm doing wrong? I am new to typescript so please guide me if necessary. I am trying to pass the context value of state and dispatch to the child component. I am not sure what's going. Is this also a correct way to implement useContext and useReducer hooks?
Upvotes: 6
Views: 8081
Reputation: 74490
I am trying to pass the context value of state and dispatch to the child component.
The MenuContext
type is determined by initialState
- they don't match up in your case. You could declare a custom context value type:
type StoreApi = {
state: typeof initialState
dispatch: React.Dispatch<Action>
}
Then define MenuContext
like this:
// undefined is just the default in case, you don't have a provider defined
export const MenuContext = React.createContext<StoreApi | undefined>(undefined)
// or if you want it more like your first example (I would prefer first variant)
export const MenuContext = React.createContext<StoreApi | typeof initialState>(initialState)
Is this also a correct way to implement useContext and useReducer hooks?
Above is a common pattern to initialize a React context. Further extension points:
1.) Currently, all your context consumers re-render each render phase, as value
is a new object reference every time. If you have slow performance (no premature optimization), memoize value
:
const value = React.useMemo(() => ({ state, dispatch }), [state])
2.) Create a custom useMenu
hook, that returns state
, dispatch
and other useful API methods.
function useMenu() {
const context = React.useContext(MenuContext);
if (context === undefined) throw new Error(`No provider for MenuContext given`);
const { state, dispatch } = context;
const myApiMethod = () => dispatch({ type: "SET_ACTIVE", label: "foo" })
return { ...context, myApiMethod };
}
const Client = () => {
const { state, dispatch, myApiMethod } = useMenu()
// ...
}
This way MenuContext
is encapsulated, there is no need to use React.useContext(MenuContext)
and the client gets a tailored API. Further reading: How to use React Context effectively and its related articles.
Upvotes: 4
Reputation: 711
Your problem is that your context is supposed to have the same type as initialState
, but instead, you set your context's default value to { state, dispatch }
.
This is incorrect.
To solve, either set your context's default type to { state, dispatch }
(I think this is what you want, or set the context's default type to typeof initialState
.
Here's the solution.
Upvotes: 6