4

I am refactoring to TypeScript a tutorial in ReactJS that I am following. For the most part, the tutorial teaches you how to refactor most of the code as bonus material. However the log-in part is done in ReactJS/JavaScript only and I tried to refactor my code as a way to challenge myself. The part I am stuck on is createContext as I am not able to understand the types needed.

Original JS code

JS code - Context/Provider

import React, { useState, createContext } from "react";


export const Context = createContext();

const UserProvider = ({ children }) => {
    const [state, setState] = useState(undefined);

    return (
        <Context.Provider value={[state, setState]}>{children}</Context.Provider>
    )
};

export default UserProvider;

On the Login component it gets called with const [_user, setUser] = useContext(Context);

My attempt

What I've tried

I tried to apply the following solutions and I'm not really grasping the concepts well enough to apply them successfully:

TS code - Context/Provider

import React, { useState, createContext } from "react";
import { IUser, UserContextType } from "./@types/context";


export const Context = createContext<UserContextType | undefined>(undefined);

const UserProvider: React.FC<React.ReactNode> = ({ children }) => {
    const [state, setState] = useState<IUser>();

    return (
        <Context.Provider value={{state, setState}}>{children}</Context.Provider>
    )
};

export default UserProvider;

TS code - types

export interface IUser {
    user: string;
    password: string;
};

export type UserContextType = {
    state: IUser;
    setState: (newSession: IUser) => void;
};

Errors

<Context.Provider value={{state, setState}}>{children}</Context.Provider>

Type 'IUser | undefined' is not assignable to type 'IUser'. Type 'undefined' is not assignable to type 'IUser'.ts(2322)

const [_user, setUser] = useContext(Context);

Type 'UserContextType | undefined' is not an array type.ts(2461)

7
  • 2
    The context does not have state or setState in its type. Did you mean to use those names instead of sessions and saveSession? Also, since you're passing in an object as the context value, the return value of useContext will also be an object (with those properties), not an array (so don't use [] brackets) Commented Mar 11, 2022 at 20:03
  • @CertainPerformance I edited the code based on what I think you are suggesting. My guess was that there wasn't a direct relationship between the name of the types and the useState variables created. About the brackets [] not sure if you mean only on the useState or overall. Commented Mar 11, 2022 at 20:22
  • are the errors still existing, if yes please update your question? Commented Mar 11, 2022 at 20:32
  • 1
    I see that the <Context.Provider /> is different in TS code - Context/Provider and Errors code snippets. I suggest you using what is in TS code - Context/Provider Commented Mar 11, 2022 at 20:45
  • 1
    You're now passing an array as the context value instead of an object here: value={[state, setState]} Decide on either an array or an object, then use that schema everywhere Commented Mar 11, 2022 at 20:47

2 Answers 2

5

First of all, you declare the context type like so:

export const Context = createContext<UserContextType | undefined>(undefined);

This means that the value of the context is either an object with all properties present of UserContextType or it is undefined.

Then here:

const [state, setState] = useState<IUser>();

You create state that has a type of IUser. But because you do not provide a default value, the default value is undefined. This is added to the state's type for you. So state is of type IUser | undefined.

Then we get here:

value={{state, setState}}

state is IUser | undefined, but the context type expects an IUser[] for that property. So neither IUser or undefined are valid types there.


First, you need to make sure that if you are passing in an object, then the object is correctly typed. That means that it has all required properties present. Else, pass in undefined. You can do this with a ternary expression:

value={state ? {state, setState} : undefined}

Now if state has a value, then the context value is created and passed it. Otherwise the context value is undefined.


But now you get this error:

    Types of property 'state' are incompatible.
      Type 'IUser' is missing the following properties from type 'IUser[]': length, pop, push, concat, and 26 more.(2322)

This is because you trying to assign a single user IUser to an array of users IUser[].

It looks like you mean for the content to only hold a single user, so you probably want to change the contexts type to:

export type UserContextType = {
    state: IUser;
    setState: (newSession: IUser) => void;
};

Which works, see playground

Or you need to pass in an array of users:

const UserProvider: React.FC<React.ReactNode> = ({ children }) => {
    const [state, setState] = useState<IUser[]>([]);

    return (
        <Context.Provider value={{state, setState}}>{children}</Context.Provider>
    )
};

Which also works, see playground


You could also change the context type to:

export type UserContextType = {
    state?: IUser;
    setState: (newSession: IUser) => void;
};

Which makes state optional, and then:

const UserProvider: React.FC<React.ReactNode> = ({ children }) => {
    const [state, setState] = useState<IUser>();

    return (
        <Context.Provider value={{state, setState}}>{children}</Context.Provider>
    )
};

Should work fine, see playground

Sign up to request clarification or add additional context in comments.

2 Comments

I know is not the expected comment of SO but I want to deeply thank you for the effort you have put into showing me this. Not only now works but now I have a much better understanding of createContext
I'll allow it :) You're welcome.
-2

This is an example of a Context on Typescript

AuthContext.tsx:

import { createContext, ReactNode, useContext, useState } from "react";
import { AuthContextData } from "../models/AuthContextData.model";

const AuthContext = createContext<AuthContextData>({} as AuthContextData);

export const AuthProvider = ({ children }: { children: ReactNode }) => {
  const [user, setUser] = useState<object | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  async function signIn(): Promise<void> {
   console.log('sign in')
  }

  async function signOut(): Promise<void> {
    console.log('sign out')
  }

  return (
    <AuthContext.Provider
      value={{ signed: !!user, user, signIn, signOut, loading }}
    >
      {children}
    </AuthContext.Provider>
  );
};

export function useAuth() {
  const context = useContext(AuthContext);
  return context;
}

AuthContextData.ts:

export interface AuthContextData {
  signed: boolean;
  user: object | null;
  signIn(): Promise<void>;
  signOut(): Promise<void>;
  loading: boolean;
}

2 Comments

Not gonna lie, this confuses me a bit more, and rises some questions. How do I type check the input of the user as string? as I'm understanding it is an object or a null. Also How do I access from that object the password or the username?
That is an example of context, sure. But it doesn't explain why the OP is having problems.

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.