3
votes

I am trying to set up a React store using useReducer and useContext hooks. The React.createContext(defaultValue) is creating issues with my TS checker. I have tried a few different things, but essentially, I createContext(null) then in the component useReducer() to set state and dispatch, but when I call the Provider and pass the value as {state, dispatch}, it doesn't tells me "Type '{ state: any; dispatch: React.Dispatch; }' is not assignable to type 'null'.

I don't understand this because by the time it errors, I have assigned state and dispatch and the value should no longer be null.

Here is the Context Provider wrapper that I am trying to create.

import React, { createContext, useReducer, FC, Dispatch } from 'react';
import storeReducer, { initialState } from './reducer';
import { Action, State } from './types';
import { isNull } from 'util';

const StoreContext = createContext(null);

const StoreProvider:FC = ({ children }) => {
  const [state, dispatch] = useReducer(storeReducer, initialState);
  const value = {state, dispatch}
  return (
    <StoreContext.Provider value={value}>
      {children}
    </StoreContext.Provider>
  );
};

export { StoreProvider, StoreContext };

export interface IStoreContext {
  dispatch: (action: Action<any>) => {};
  state: State;
}

if I leave it as simply const StoreContext = createContext();, then it complains that defaultValue is not being defined.

The crazy thing is I have lifted this from an old project and had no issues compiling.

3

3 Answers

5
votes

When you initialize a context with null, the intended type can't be inferred. You have to give the type of the context explicitly in this case.

Here that would look something like:

const StoreContext = createContext<{
  state: MyStateType,
  dispatch: Dispact,
} | null>(null);
2
votes

Since I just made an account to answer this, I can't comment above. Alex Wayne is correct, that is acceptable to Typescript. However, the useContext hook won't really work for this because as far as Typescript is concerned, you may have a null value in that Context.

From original answer... here is our Context in Typescript const StoreContext = createContext<{state: MyStateType, dispatch: Dispatch} | null>(null);

So, we need to create a new hook, let's call it useContextAndErrorIfNull. This would look like:

const useContextAndErrorIfNull = <ItemType>(context: Context<ItemType | null>): ItemType => {
  const contextValue = useContext(context);
  if (contextValue === null) {
    throw Error("Context has not been Provided!");
  }
  return contextValue;
}

Use this hook instead of useContext and it should all work well.

0
votes

This may be a case where it is okay to temporarily cast it in order to save yourself a hassle:

interface Store {
  state: MyStateType,
  dispatch: Dispact,
}

const StoreContext = createContext<Store>({} as Store);

const StoreProvider = ({children}) => {
  const {state, dispatch} = theseAreAlwaysPresent()

  return (
    <StoreContext value={{state, dispatch}}>
      {children}
    </StoreContext.Provider>
  )
}
...