Reputation: 46479
Code below demonstrates how I'm trying to implement react's context with react hooks, idea here is that I will be able to easily access context from any child component like this
const {authState, authActions} = useContext(AuthCtx);
To begin with I create a file that exports context and provider.
import * as React from 'react';
const { createContext, useState } = React;
const initialState = {
email: '',
password: ''
};
const AuthCtx = createContext(initialState);
export function AuthProvider({ children }) {
function setEmail(email: string) {
setState({...state, email});
}
function setPassword(password: string) {
setState({...state, password});
}
const [state, setState] = useState(initialState);
const actions = {
setEmail,
setPassword
};
return (
<AuthCtx.Provider value={{ authState: state, authActions: actions }}>
{children}
</AuthCtx.Provider>
);
}
export default AuthCtx;
This works, but I get error below in value
of provider, probably because I add actions in, hence the question, is there a way for me to keep everything typed and still be able to export context and provider?
I beliebe I also can't place createContext
into my main function since it will re-create it all the time?
[ts] Type '{ authState: { email: string; password: string; }; authActions: { setEmail: (email: string) => void; setPassword: (password: string) => void; }; }' is not assignable to type '{ email: string; password: string; }'. Object literal may only specify known properties, and 'authState' does not exist in type '{ email: string; password: string; }'. [2322] index.d.ts(266, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<{ email: string; password: string; }>' (property) authState: { email: string; password: string; }
Upvotes: 9
Views: 18793
Reputation: 616
The answer above works when strict rules are disabled. There is an example for context with enabled strict rules:
import { createContext, Dispatch, SetStateAction, useState } from 'react';
import { Theme } from '@styles/enums';
import { Language } from '@common/enums';
type Props = {
children: React.ReactNode;
};
type Context = {
appLang: string;
appTheme: string;
setContext: Dispatch<SetStateAction<Context>>;
};
const initialContext: Context = {
appLang: Language.EN,
appTheme: Theme.DEFAULT,
setContext: (): void => {
throw new Error('setContext function must be overridden');
},
};
const AppContext = createContext<Context>(initialContext);
const AppContextProvider = ({ children }: Props): JSX.Element => {
const [contextState, setContext] = useState<Context>(initialContext);
return (
<AppContext.Provider value={{ ...contextState, setContext }}>
{children}
</AppContext.Provider>
);
};
export { AppContext, AppContextProvider };
It works for me. Theme
and Language
are just enums, like this one:
export enum Theme {
DEFAULT = 'DEFAULT',
BLACK = 'BLACK',
}
And also I provided an Error function setContext
into initialContext
to throw error if programmer doesn't define setContext
in Provider
. You can just use:
setContext: (): void => {}
Good luck!
Upvotes: 33
Reputation: 281686
While creating Context, you are providing an initial value to it. Provide it in the same format as you expect it to be for the Provider like:
const initialState = {
authState : {
email: '',
password: ''
},
authActions = {
setEmail: () => {},
setPassword: () => {}
};
};
const AuthCtx = createContext(initialState);
Also, you don't even need the initialState since its only passed to Consumer, if you don't have a Provider higher up in the hierarchy for the Consumer.
Upvotes: 18