4

I'm working on implementing TypeScript on a small codebase I've been working on and having a few troubles with the above error. I've searched for answers but none seemed to fix the actual issue I was having.

I'm getting the error:

Type 'ContextType | null' is not assignable to type 'ContextType'.

I have tried setting it to null, undefined, and object, and nothing seems to help so I'd appreciate some help with this one!

Store.txt

import { useReducer, createContext, useMemo } from "react";
import { INITIAL_DATA } from './todo/constants';
import { ContextType, ITodo, ACTIONTYPE } from './todo/models';
export const StoreContext = createContext<ContextType | null>(null)

export enum ACTIONS {
  DELETE_TODO = "delete_todo",
  ADD_TODO = "add_todo",
};

const reducer = (state: ITodo, action: ACTIONTYPE) => {
  switch (action.type) {
    case ACTIONS.DELETE_TODO:
      return [...action.payload];
    case ACTIONS.ADD_TODO:
      return action.payload;
    default:
      return state;
  }
};

interface Props {
  children: React.ReactNode;
}

export const StoreProvider = ({ children }: Props) => {
  const [state, dispatch] = useReducer(reducer, INITIAL_DATA);
  const contextValue: ContextType = useMemo(() => {
    return { state, dispatch };
  }, [state, dispatch]);

  return (
    <StoreContext.Provider value={contextValue}>
      {children}
    </StoreContext.Provider>
  );
};

Component.tsx

import { useContext } from 'react';

import { StoreContext, ACTIONS } from '../../../store';
import { ContextType } from '../../models'


export const AddTodo = () => {
  const { state, dispatch }: ContextType = useContext(StoreContext);

  const validateFirstStep = async () => {
    return await isValid(firstStepForm);
  }

  const closeDrawer = () => {
    onClose();
  }

  const handleSubmit = async () => {
    const newTodoEntry = { /** todo **/ }
    const newData = [...state, newTodoEntry];

    dispatch({ type: ACTIONS.ADD_TODO, payload: newData });
  }

  return (
    <div>
     { /* Something Todo happens here */ }
    </div>
  )
}

Models.tsx

import { ACTIONS } from '../../store';

export type ITodos = {
  id: string;
  todoName: string;
  todoType: string;
}[];

export type ContextType = {
  state: ITodos;
  dispatch: React.Dispatch<ACTIONTYPE>;
}

export type ACTIONTYPE =
  | { type: ACTIONS.ADD_TODO, payload: ITodos }
  | { type: ACTIONS.DELETE_TODO; payload: ITodos }

2 Answers 2

5

You need to provide default context value in case of there is no provider higher in the react tree;

export const StoreContext = createContext<ContextType>({todos: [], dispatch: () => {}})
Sign up to request clarification or add additional context in comments.

2 Comments

woudn't this dispatch: () => {} be considered a hack?
For anyone wondering about the above I came across stackoverflow.com/questions/49949099/… which seems to say that the initial value is only used when trying to access it when outside a provider which you generally would not do. Here it is a hack to satisfy TypeScript
0

I just followed https://blog.logrocket.com/how-to-use-react-context-typescript/ and they do:

React.createContext<TodoContextType | null>(null);

Then explicitly cast the useContext where they want to make use of it:

const { saveTodo } = React.useContext(TodoContext) as TodoContextType;

I think this might work the same (and is more succinct)?

const { saveTodo } = React.useContext(TodoContext)!;

I created a custom hook that did the cast for me - so I didn't have to remember to cast it in the consumer:

export const useTodoContext = () => React.useContext(TodoContext) as TodoContextType;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.