Search code examples
reactjsreact-context

React.useContext(...) is undefined


I tried using useContext for the first time and don't know what I'm doing wrong, the bold lines are the ones with the (Uncaught TypeError: React.useContext(...) is undefined). I have imported the usercontext and checked if the file location is right too, even updated the dependencies. What I'm doing wrong?

UserContext:


import React from 'react';
import { TOKEN_POST, USER_GET } from 'Api';

export const UserContext = React.createContext();

export const UserStorage = ({ children }) => {
  const [data, setData] = React.useState(null);

  async function getUser(token) {
    const { url, options } = USER_GET(token);
    const response = await fetch(url, options);
    const json = await response.json();
    setData(json);
    setLogin(true);
    console.log(json);
  }

  async function userLogin(username, password) {
    const { url, options } = TOKEN_POST({ username, password });
    console.log(url);
    console.log(options);
    const tokenRes = await fetch(url, options);
    const { token } = await tokenRes.json();
    window.localStorage.setItem('token', token);
    getUser(token);
  }

  return (
    <UserContext.Provider value={{ userLogin, data }}>
      {children}
    </UserContext.Provider>
  );
};

Header:

import React from 'react';
import styles from './Header.module.css';
import { UserContext } from '../UserContext';

const Header = () => {

  const { data } = React.useContext(UserContext);
  
  return (
    <header className={styles.header}>
      <nav className={`${styles.nav} container`}>
        <Link className={styles.logo} to="/" aria-label="Dogs - Home">
          <Dogs />
        </Link>
        { data && data.email }
        <Link className={styles.login} to="/login">
          Login / Criar
        </Link>
      </nav>
    </header>
  );
};

export default Header;

LoginForm:

import React from 'react';
import { Link } from 'react-router-dom';
import Input from '../Forms/Input';
import Button from '../Forms/Button';
import useForm from '../../Hooks/useForm';
import { UserContext } from '../../UserContext';

const LoginForm = () => {
  const username = useForm();
  const password = useForm();

  const { userLogin } = React.useContext(UserContext);

  async function handleSubmit(event) {
    event.preventDefault();

    if (username.validate() && password.validate()) {
      userLogin(username.value, password.value)
    }
  }

  return (
    <section>
      <h1>Login</h1>
      <form action="" onSubmit={handleSubmit}>
        <Input label="Usuário" type="text" name="username" {...username} />
        <Input label="Senha" type="password" name="password" {...password} />
        <Button>Entrar</Button>
      </form>
      <Link to="/login/criar">Cadastro</Link>
    </section>
  );
};

export default LoginForm;


Solution

  • I think you are not wrapping children/pages which are using this context. Make sure you have wrapped UserStorageProvider in your App.js.

    You can do something like:

    
    /// Update your context file like this
    
    import React, { useContext } from 'react';
    import { TOKEN_POST, USER_GET } from 'Api';
    
    const UserContext = React.createContext();
    
    export const UserStorageProvider = ({ children }) => {
      const [data, setData] = React.useState(null);
    
      async function getUser(token) {
        const { url, options } = USER_GET(token);
        const response = await fetch(url, options);
        const json = await response.json();
        setData(json);
        setLogin(true);
        console.log(json);
      }
    
      async function userLogin(username, password) {
        const { url, options } = TOKEN_POST({ username, password });
        console.log(url);
        console.log(options);
        const tokenRes = await fetch(url, options);
        const { token } = await tokenRes.json();
        window.localStorage.setItem('token', token);
        getUser(token);
      }
    
      return (
        <UserContext.Provider value={{ userLogin, data }}>
          {children}
        </UserContext.Provider>
      );
    };
    
    /// create this custom hook to read UserStorage context
    export const useUserStorage = ()=> useContext(UserContext);
    

    In your App.js or layout (if you are using) do as follow:

    
    import { UserStorageProvider } from 'your/path'
    
    export default function App() {
      return (
        <UserStorageProvider>
          <Header />
          <LoginPage />
        </UserStorageProvider>
      )
    }
    

    And in your component you can use the custom hook as follow:

    Header

    
    import React from 'react';
    import styles from './Header.module.css';
    import { useUserStorage } from '../UserContext';
    
    const Header = () => {
    
      const { data } = useUserStorage()
    
    ....
    }