Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useContext + useReducer with Typescript

I am "translating" this react example into typescript: https://react.dev/learn/scaling-up-with-reducer-and-context#step-1-create-the-context

without success so far! The UserUpdateContext "value" is giving me this error in Visual Studio:

(Type 'ActionDispatch<[action: Action]>' is not assignable to type '[action: Action]'.ts(2322)
index.d.ts(536, 9): The expected type comes from property 'value' which is declared here on type 'IntrinsicAttributes & ProviderProps<[action: Action] | null>')


<UserUpdateContext.Provider value={dispatch}>

Also useUserDispatch() is returning an action rather than a function accepting an action as parameter.

I tried numerous variations but any typescript / react example is doing it a bit different and no one has used ActionDispatch or Dispatch types here.

I am stuck :)

import { createContext, useContext, useReducer, type ActionDispatch, type Dispatch, type ReactNode } from 'react';
import {type User} from "./user"
import { UserFactory } from './unit';



export type Action = 
  | { type: "created", value: User}
  | { type: "changed" | "deleted", value: any }


const UserContext = createContext<User|null>( null);
// ActionDispatch<[action: Action]
// const UserUpdateContext = createContext<any>( {type: "created", value: UserFactory.createUser()});
const UserUpdateContext = createContext< [action: Action]|null>( null);



export function UserProvider({ children }:{children: ReactNode | ReactNode[]}) {


  const [user, dispatch] = useReducer(
    userReducer,              // reducer
    UserFactory.createUser()  // initial state
  );


  return (
    <UserContext value={user}>
      <UserUpdateContext.Provider value={dispatch}>
        {children}
      </UserUpdateContext.Provider>
    </UserContext>
  )
}

export function useUser() {
  return useContext(UserContext);
}

export function useUserDispatch() {
  return useContext(UserUpdateContext);
}


function userReducer(user:User, action:Action) {
  switch (action.type) {
    case "created": {
      return action.value
    }
// other stuff
    case 'changed': {
    }
  }
}


like image 655
stot Avatar asked Aug 03 '26 15:08

stot


1 Answers

useReducer is defined as:

export function useReducer<S, I, A>(
  reducer: (S, A) => S,
  initialArg: I,
  init?: I => S,
): [S, Dispatch<A>] {
  const dispatcher = resolveDispatcher();
  return dispatcher.useReducer(reducer, initialArg, init);
}

The return type of your useReducer(userReducer, UserFactory.createUser()) is an array of your User state type and your Action type wrapped in React's Dispatch, e.g.:

[User, Dispatch<Action>]

So these are the types you should provide to your context providers respectively, e.g.:

import {
  createContext,
  type Dispatch
} from "react";

type Action =
  | { type: "created"; value: User }
  | { type: "changed" | "deleted"; value: any };

const UserContext = createContext<User | null>(null);

const UserUpdateContext = createContext<Dispatch<Action> | null>(null);

Your UserProvider component might also be a bit malformed. If you are using React 19 rendering the context directly will render the Provider component, otherwise if you are using an older version it should use UserContext.Provider to provide the User value instead of just UserContext.

import {
  PropsWithChildren,
  useReducer,
} from "react";

function UserProvider({ children }: PropsWithChildren) {
  const [user, dispatch] = useReducer(userReducer, UserFactory.createUser());

  return (
    <UserContext.Provider value={user}>
      <UserUpdateContext.Provider value={dispatch}>
        {children}
      </UserUpdateContext.Provider>
    </UserContext.Provider>
  );
}
like image 55
Drew Reese Avatar answered Aug 05 '26 08:08

Drew Reese