0

Here I want to create an AuthContext to share user state to other components. Here I am using TypeScript to set the variable type. But I get a lot of errors when trying to solve this problem. And I very confused with this problem.

This my AuthContext:

import { createContext, ReactNode, useReducer } from 'react'
import { AuthReducer } from './Reducer';

export interface IState {
    isAuthenticated: boolean
    user: string | null
    token: string | null
}

// interface for action reducer
export interface IAction {
    type: 'LOGIN' | 'LOGOUT' | 'REGISTER' | 'FORGOT PASSWORD'
    payload?: any
}

interface IAuthProvider {
    children: ReactNode
}

const initialState = {
    isAuthenticated: false,
    user: '',
    token: ''
}

export const AuthContext = createContext<IState>(initialState);


export const AuthProvider = ({ children }: IAuthProvider) => {
    const [state, dispatch] = useReducer(AuthReducer, initialState)

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

And this is my reducer:

import { IAction, IState } from ".";



export const AuthReducer = (state: IState, action: IAction) => {
    switch (action.type) {
        case 'LOGIN':
            localStorage.setItem("user", action.payload.user)
            localStorage.setItem("token", action.payload.token)
            return {
                ...state,
                isAuthenticated: true,
                user: action.payload.user,
                token: action.payload.token
            }

        case 'LOGOUT':
            localStorage.clear()
            return {
                ...state,
                isAuthenticated: false,

            }
    }

}

And this is my error in useReducer's line:

No overload matches this call.
  Overload 1 of 5, '(reducer: ReducerWithoutAction<any>, initializerArg: any, initializer?: undefined): [any, DispatchWithoutAction]', gave the following error.
    Argument of type '(state: IState, action: IAction) => { isAuthenticated: boolean; user: any; token: any; } | undefined' is not assignable to parameter of type 'ReducerWithoutAction<any>'.
  Overload 2 of 5, '(reducer: (state: IState, action: IAction) => { isAuthenticated: boolean; user: any; token: any; } | undefined, initialState: never, initializer?: undefined): [...]', gave the following error.
    Argument of type '{ isAuthenticated: boolean; user: string; token: string; }' is not assignable to parameter of type 'never'.ts(2769)

And this is my error in line <AuthContext.Provider value={{ state, dispatch }}>:

Type '{ state: any; dispatch: React.DispatchWithoutAction; }' is not assignable to type 'IState'.
  Object literal may only specify known properties, and 'state' does not exist in type 'IState'.ts(2322)

does anyone know the solution or tell what happened? thank you

Edited: Here I have another problem when I use my context on Login page. This is my LoginPage:

import React, { FC, useContext } from 'react'
import { AuthContext } from '../AuthContext'


export const Login: FC<IProps> = () => {
    const { state, dispatch } = useContext(AuthContext)
    const login = () => {
        dispatch({
            type: 'LOGIN',
            payload: { ...state, isAuthenticated: !state.isAuthenticated }
        })
    }
    return (
        <div>
            <input name="username" />
            <button onClick={login}></button>
        </div>
    )
}

and when I running the code, it is show error like this:

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.

I've read the documentation, but I don't think I'm breaking any hook rules there. Where did I go wrong sorry I'm new to react.

Raazescythe
  • 117
  • 1
  • 20
  • 1
    The initial value of your context is just the state, but it needs to be an object with both state and dispatch. – Linda Paiste Sep 12 '21 at 15:01
  • 1
    I seem to have misunderstood what I should put in the context, Thank you very much for your help, it really helped me – Raazescythe Sep 13 '21 at 00:46

2 Answers2

5

There is a mismatch between the context that you are creating and the context that you are using.

You create a context whose value is a state object with type IState:

export const AuthContext = createContext<IState>(initialState);

You use a context whose value is an object with properties state and dispatch:

<AuthContext.Provider value={{ state, dispatch }}>

In order to fix this mismatch, you must define your AuthContext in a way that matches how you want to use it. The context value needs to be an object with properties state and dispatch.

Here we define an interface for the context value. The default value of the state is the initialState while the default value for dispatch logs an error message.

interface AuthContextValue {
    state: IState;
    dispatch: React.Dispatch<IAction>;
}

export const AuthContext = createContext<AuthContextValue>({
    state: initialState,
    dispatch: (action) => console.error("Dispatched action outside of an AuthContext provider", action)
});

TypeScript Playground Link

Here's a simpler version, where we define the context value type inline and do nothing when dispatch is called with the default value.

export const AuthContext = createContext<{state: IState; dispatch: React.Dispatch<IAction>}>({
    state: initialState,
    dispatch: () => {},
});

TypeScript Playground Link


I get an unrelated error on your reducer, TS 7030 "Not all code paths return a value." A reducer should always have a default case in the switch to avoid returning an undefined state.

default: return state;
Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • I have another problem, when I call `createContext` in Login Page, I have error like this: `Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons: 1. You might have mismatching versions of React and the renderer (such as React DOM) 2. You might be breaking the Rules of Hooks 3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem` may I ask again? – Raazescythe Sep 13 '21 at 02:51
1

The problem is you are passing state and dispatch as values to the provider but when you defined the context you are only passing Istate as it’s type. Your Createcontext should include both state and dispatch as types. That will change the initial value as well.

The type of the value you provide to the provider and the type of your context should be the same.

Ex: https://www.pluralsight.com/guides/using-react's-context-api-with-typescript

Sanish Joseph
  • 2,140
  • 3
  • 15
  • 28