Using react context with react hooks in typescript
Asked Answered
M

2

9

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

Mundt answered 16/11, 2018 at 10:25 Comment(2)
Where are you typing your contextYount
@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 renderMundt
Y
18

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.

Yount answered 16/11, 2018 at 10:50 Comment(1)
when i use usestate in my context i see this error .Type 'Dispatch<any>' is not assignable to type '() => voidAstir
H
33

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!

Hysterectomy answered 12/9, 2019 at 14:2 Comment(1)
This should be marked as the correct answer, thank you.Multicolor
Y
18

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.

Yount answered 16/11, 2018 at 10:50 Comment(1)
when i use usestate in my context i see this error .Type 'Dispatch<any>' is not assignable to type '() => voidAstir

© 2022 - 2024 — McMap. All rights reserved.