Getting TS Error: Property 'user' does not exist on type 'IAuth | null'

Viewed 1042

I am using context for Auth in my simple typescript app. My context declaration is in the file AuthProvider.tsx, which looks like:

import React, { createContext, useState } from 'react';
import auth from '@react-native-firebase/auth';

export interface IAuth {
    user,
    setUser,
    login,
    register,
    logout
}

export const AuthContext = createContext<IAuth | null>(null);

export const AuthProvider = ({ children }) => {
    const [user, setUser] = useState(null);

    return (
        <AuthContext.Provider
            value={{
                user: user,
                setUser: setUser,
                login: async (email, password) => {
                    try {
                        await auth().signInWithEmailAndPassword(email, password);
                    } catch (e) {
                        console.log(e);
                    }
                },
                register: async (email, password) => {
                    try {
                        await auth().createUserWithEmailAndPassword(email, password);
                    } catch (e) {
                        console.log(e);
                    }
                },
                logout: async () => {
                    try {
                        await auth().signOut();
                    } catch (e) {
                        console.log(e);
                    }
                },
            }}>
            {children}
        </AuthContext.Provider>
    );
};

This is how I am using it elsewhere:

  const { user, setUser } = useContext(AuthContext);

But Typescript throws 2 errors on this lien:

  1. Property 'user' does not exist on type 'IAuth | null'
  2. Property 'setUser' does not exist on type 'IAuth | null'

What can I do to bypass it?

4 Answers

Right here, you have said that contents of your AuthContext can be either an IAuth object or it can be null.

export const AuthContext = createContext<IAuth | null>(null);

You cannot destructure the value of useContext(AuthContext) as if it were an object because you have not verified that it is an actually an IAuth and not null.

There are many ways to address this.

One approach is that you can still allow for the context value to be null, but we have to check before using it. This works and is easy to implement. We can exit the component early by returning null if the values don't exist. But you must make sure that you exit only after all other hooks have been called or else you'll get a "violated the rules of hooks" error.

export const Consumer = () => {
   const context = useContext(AuthContext);
   if ( ! context ) {
     return null;
   }
   const { user, setUser } = context;

   return (
     <div/>
   )
}

Another approach is that we allow for an empty or incomplete object instead of null. This allows us to destructure, but we are just moving the errors down the chain because those two value that we destructured user and setUser might have the expected values or might be undefined.

export const AuthContext = createContext<Partial<IAuth>>({});
const { user, setUser } = useContext(AuthContext);

A more verbose approach is to create a default value for the context which fulfills the IAuth interface by using dummy methods. These functions should be callable so that won't produce errors but they don't do anything.

const defaultValue: IAuth = {
  setUser: () => console.error("attempting to use AuthContext outside of a valid provider"),
  /*... */
}

export const AuthContext = createContext<IAuth>(defaultValue);

Does your interface really look like what you've posted, or did you just remove the values for the sake of the question? An interface needs to define the type associated with each key, not just list the keys.

I guess you can cast it to IAuth with const { user, setUser } = useContext(AuthContext) as IAuth

You need to provide a default value when creating the context but you are creating it with a null.

In your case you are probably accessing it from a different component using the useContextHook

Think of this scenario, if you directly access it without providing a value in provider level and you access the user or other fields from the context it wont be there. so it complains saying that the values are not present, to fix this you have to provide a default value like below (You should set the other fields in IAuth as well).

export const AuthContext = createContext<IAuth | null>({user:{},setUser:()=>{}});

The inference is correct, the context is just possibly null. Do a null check:

const ctx = useContext(AuthContext);
if(ctx !== null) {
  const { user, setUser } = ctx;
  // ...
}
Related