Search code examples
reactjstypescriptreact-hooks

Typescript issue and reducer state doesn't add use on click. Property 'state and dispatch' does not exist on type 'UserContextType | null'


I am new to typescript with react and tried so many thing but doesn't work. state doesn't work and getting typescript error Property 'state and dispatch' does not exist on type 'UserContextType | null' UserContRed.tsx

export const UserContRed = () => {

   //const context = useContext(ContRedContext)
    const {state , dispatch } = useContext(ContRedContext)
   
    const [firstname, setFirstname] = useState('')
    const [id, setId ] = useState(0)
    const [lastname, setLastname] = useState('')
 
    // let displayUsers =  context?.state.users.map( (u:any) => {
    let displayUsers =  state.users.map( (u:any) => {
        return(
            <ul key={u.id}>
                <li>{u.id}</li>
                <li>{u.firstname}</li>
                <li>{u.lastname}</li>
            </ul>
        ) 
    }) 
    const addUser = () => {
        const addUser = () => dispatch({type:ActionType.ADD, payload : {id:1,firstname:firstname, lastname:lastname}})
    }

    return(
        <div>
          <div>
            <label>Id: </label>
            <input type="text" onChange={(e)=>{setId(Number(e.target.value))}} />
          </div>
          <div>
            <label>Firstname: </label>
            <input type="text" onChange={(e)=>{setFirstname(e.target.value)}} />
          </div>
          <div>
            <label>Lastname: </label>
            <input type="text" onChange={(e)=>{setLastname(e.target.value)}} />
          </div>
            <button onClick={addUser}>sendToStore</button>

          
        </div>
       
    )
}

ContRed.tsx


export type User = {
    id: number;
    firstname: string;
    lastname: string; 
}

type contredProps = {
    children: React.ReactNode
}


export type IuserState = {
    users : User[]
}
const initialState: IuserState = {
    users : [
        {
            id:0,
            firstname:'',
            lastname: ''
        }
    ]
}
export enum ActionType {
    ADD="add",
    REMOVE="remove"
}


type  userAction = {
    type: ActionType,
    payload: User
}


export const reducer = (state: IuserState, action: userAction) : typeof initialState  => {
    
    switch(action.type){
        case "add": 
                return ({...state, users: [...state.users,
                                                    {id:action.payload.id,
                                                     firstname:action.payload.firstname,
                                                     lastname:action.payload.lastname   
                                                    }
                                                ]
                        })
        case "remove": 
                        return ({...state, users: [
                                    ...state.users.filter(data => data.id !== action.payload.id)]
                                })
        default:
            return state;
    }
}


type UserContextType = {
    state: IuserState
    dispatch: React.Dispatch<userAction>
}

export const ContRedContext = createContext<UserContextType|null>({
    state:initialState,
    dispatch: () => {}
})



export const ContRedContextProvier = ({children}:contredProps) => {
    
    const[state,dispatch] = useReducer(reducer,initialState as IuserState);
    const value : UserContextType = { state, dispatch}
    
    return(
        <ContRedContext.Provider value={value}>
            {children}
        </ContRedContext.Provider>
    )

}

App.tsx

    <ContRedContextProvier>
        <UserContRed/>
    </ContRedContextProvier>
    

I tried with using constant instead de-structuring typescript error goes away but it doesn't add or doesn't update the state. typescript is so hard seems to me with react please explain or guide what i am doing wrong. Below tried typescript is fine but functionality doesn't work

  const context = useContext(ContRedContext)
  let displayUsers =  context?.state.users.map( (u:any) => { ...}
   const addUser = () => context?.state.dispatch({type:ActionType.ADD, payload : {id:1,firstname:firstname, lastname:lastname}})

Tried:

 const context = useContext(ContRedContext)
  let displayUsers =  context?.state.users.map( (u:any) => { ...}
   const addUser = () => context?.state.dispatch({type:ActionType.ADD, payload : {id:1,firstname:firstname, lastname:lastname}})

Expected: User should add to state and render in child component.


Solution

  • You have typed the ContRedContext to be nullable, e.g. createContext<UserContextType | null>({ .... }) , so consumers would need to use a null-check prior to accessing state or dispatch.

    Example:

    const context = useContext(ContRedContext);
    
    if (!context) return null;
      
    const { state, dispatch } = context; // not null 🙂
    

    But you don't appear to ever pass a null context value, even for the default context value, and likely have no need to.

    Update the ContRedContext context type declaration to remove the null type:

    export const ContRedContext = createContext<UserContextType>({
      state: initialState,
      dispatch: () => {}
    });
    

    There is an additional issue in the UserContRed component with the addUser handler. It declares a function that issues the dispatch and never calls it.

    Issue code:

    const addUser = () => {
      const addUser = () => // <-- never called
        dispatch({
          type: ActionType.ADD,
          payload: { id, firstname: firstname, lastname: lastname }
        });
    };
    

    Update to just call dispatch:

    const addUser = () => {
      dispatch({
        type: ActionType.ADD,
        payload: { id, firstname, lastname }
      });
    };