9
votes

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

2
Where are you typing your contextShubham Khatri
@ShubhamKhatri by default it inherits it from createContext(initialState) issue is that creation is outside of function, hence I can't type actions I think. And I need those actions inside my function component as they update state. If I move context inside the function, I can easily type actions, but no longer export context and I think there will be complications since react function components are re-ran on each renderIlja

2 Answers

22
votes

Answer above works, because strict rules of checking of types disabled. Example for context with 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 it just enums, like this:

export enum Theme {
  DEFAULT = 'DEFAULT',
  BLACK = 'BLACK',
}

And I send Error function in setContext inner initialContext for throwing error if programmer doesn't define setContext in Provider. You can just use

setContext: (): void => {}

Good luck!

15
votes

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.