Reputation: 793
I am working on a React webapplication with TypeScript. I want to set up State Management with React Hooks and Context API and found this cool, easy and short tutorial.
I followed the tutorial, but my compiler shows couple of errors. I think that are basic type errors, that I don't get (I am more used to JavaScript than TypeScript).
My State Context:
import React, { createContext, useContext, useReducer } from 'react';
export interface IState {
isAuth: boolean;
user: string;
}
interface IContextProps {
state: IState;
dispatch: ({ type }: { type: string }) => void;
}
export const StateContext = createContext({} as IContextProps);
export const StateProvider = ({ reducer, initialState, children }) => (
<StateContext.Provider value={useReducer(reducer, initialState)}>
{children}
</StateContext.Provider>
);
export const useStateValue = () => useContext(StateContext);
My State Reducer:
import { initialState } from './InitialState';
export const reducer = (state = initialState, action: any) => {
switch (action.type) {
case 'setObj':
return {
...state,
obj: action.objValue,
};
default:
return state;
}
};
My Initial State:
import { emptyObj } from '../interfaces/IObj';
export const initialState = {
obj: emptyObj,
};
My App:
import React from 'react';
import { StateProvider } from './utils/StateContext';
import { initialState } from './globals/constants/InitialState';
import { reducer } from './globals/constants/StateReducer';
function App() {
return (
<div className='App'>
<header className='App-header'>
<StateProvider initialState={initialState} reducer={reducer}>
<p>Hello World</p>
</StateProvider>
</header>
</div>
);
}
export default App;
The errors are only in the State Context File:
1.) State Context
First error is the State Context. In the tutorial it's defined as:
export const StateContext = createContext();
And the compiler throws an error.
If I use export const StateContext = createContext(undefined);
, then the error is gone.
Also if I use (like described at beginning) export const StateContext = createContext({} as IContextProps);
and add interfaces IState and IContextProps then it also works. I guess that part is fine for now.
2.) State Provider
export const StateProvider = ({ reducer, initialState, children }) => ...
The compiler throws for reducer, initialState and children the same error: Binding Element [reducer / initialState / children] has implicitly any type.
3.) value
value={useReducer(reducer, initialState)}>...
The compiler cries:
Type '[unknown, DispatchWithoutAction]' is not assignable to type 'IContextProps'.ts(2322)
index.d.ts(335, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps'
Thanks in advance for any help. If you need more informations then let me know.
Upvotes: 0
Views: 1372
Reputation: 979
i had the same issue recently and i solved it this way, i hope it helps you as well. i updated your code as shown in the following snippets,
export interface IState {
user: any,
theme: boolean,
drawer: boolean,
}
export const initialState = {
user: null,
theme: false,
drawer: false,
}
export enum actionTypes {
SET_THEME = "SET_THEME",
SET_USER = "SET_USER",
OPEN_DRAWER = "OPEN_DRAWER",
}
export type IAction =
| {
type: actionTypes.SET_USER,
value: any
} | {
type: actionTypes.SET_THEME,
value: boolean
}| {
type: actionTypes.OPEN_DRAWER,
value: boolean
}
const reducer = (state: IState, action:IAction): IState => {
switch (action.type) {
case actionTypes.SET_THEME:
return {
...state,
theme: action.value,
}
case actionTypes.SET_USER:
return {
...state,
user: action.value,
}
case actionTypes.OPEN_DRAWER:
return {
...state,
user: action.value,
}
default:
return state
}
}
export default reducer;
The context provider (StateProvider) should be updated to...
import React, {useReducer, createContext, useContext, ReactElement} from 'react';
import {initialState, IState} from "./Reducer";
const StateContext = createContext<IState | any>(initialState);
interface StateProps{
children: ReactElement,
initialState: (IState | any),
reducer: (IState | any),
}
export const StateProvider = ({children,initialState, reducer }: StateProps): ReactElement => {
// const [state, dispatch] = useReducer(initialState, reducer);
return(
<StateContext.Provider value={useReducer(initialState, reducer)}>
{children}
</StateContext.Provider>
);
}
export const useStateValue = () => useContext(StateContext);
Then wrap your main app with the context provider (StateProvider)
import {HeadTag, Layout} from "../components/components";
import reducer, {initialState} from "../provider/Reducer";
import {StateProvider} from "../provider/StateProvider";
function MyApp({ Component, pageProps }: AppProps) {
return (
<>
<HeadTag />
<StateProvider initialState={initialState} reducer={reducer} >
<Layout>
<Component {...pageProps} />
</Layout>
</StateProvider>
</>
);
}
export default MyApp;
Consuming it in your app
/* data layer */
const [{user, theme, drawer}, dispatch] = useReducer(reducer, initialState);
/* switch between dark and light mode */
const handleTheme = () => {
if(theme){
dispatch({
type: actionTypes.SET_THEME,
value: false,
});
}else{
dispatch({
type: actionTypes.SET_THEME,
value: true,
});
}
}
I hope this solves your problem.
Upvotes: 0
Reputation: 10071
The issue could be because of the
<StateContext.Provider value={useReducer(reducer, initialState)}>
useReducer returns an array and Provider expects an object.
const [state, dispatch] = useReducer(reducers, initialState);
return (
<StateContext.Provider value={{ state, dispatch }}>
{children}
</StateContext.Provider>
);
I have written an article on the same you can that
Article: State Management with React Hooks and Context API
Running example: https://stackblitz.com/edit/reactjs-usecontext-usereducer-state-management
Upvotes: 1