3

I'm using Typescript. I want to

  • Create a context
  • Use the context in routing
  • Update the context when logged in.

I'm following this tutorial, except Typescript cut my hopes short. See below

I have this in my App.tsx

import React from 'react';
import './App.css';

import Login from "./auth/login";
import Home from "./dash/home";
import Header from "./menu";

const initialState = {
  isAuthenticated: false,
  user: null,
  token: null,
};

export const AuthContext = React.createContext(); // <---- This right here complains: 
// Expected 1-2 arguments, but got 0.ts(2554)
// index.d.ts(349, 9): An argument for 'defaultValue' was not provided.

const reducer = (state: any, action: any) => {
  switch (action.type) {
    case "LOGIN":
      return {
        ...state,
        isAuthenticated: true,
        user: action.payload.user,
        token: action.payload.token
      };
    case "LOGOUT":
      localStorage.clear();
      return {
        ...state,
        isAuthenticated: false,
        user: null
      };
    default:
      return state;
  }
};

const App: React.FC = () => {
  const [state, dispatch] = React.useReducer(reducer, initialState);

  return (
    <AuthContext.Provider
      value={{
        state,
        dispatch
      }}
    >
      <Header />
      <div className="App">{!state.isAuthenticated ? <Login /> : <Home />}</div>
    </AuthContext.Provider>
  );
}

export default App;

In my login page, I have this:

import React from "react";
import { useForm } from 'react-hook-form';
import axios from 'axios';
import { AuthContext } from "../App";

export const Login = () => {

  const { dispatch } = React.useContext(AuthContext) // <--- This the right way to do it?
  // Where is the "dispatch" magically coming from?

  const onSubmit = (data: any) => {

    axios.post('http://localhost/api/v1/users/login/', data)
      .then(res => {
        console.log(res);
      })
  }

  return (
    <div className="login-container">
      <!-- assume there's a form here -->
    </div>
  );
};
export default Login;

So

  • What do I put in for the "defaultValue"?
  • How do I update the context after login?

EDIT:

More context (pun intended) of what I wanna achieve.

The App.tsx has an <AuthContext.Provider ...> ... </AuthContext.Provider> If I understand correctly, this Context takes in the value of the state, dispatch and as per the {!state.isAuthenticated ... } part, dynamically alternates between <Login /> and <Home/> component.

This <AuthContext.Provider/> as per the initialState is set to isAuthenticated: false

When the user logs in at the Login Component, my question is, how do I, via the AuthContext, update the values in the App.tsx so as the routing will change?

KhoPhi
  • 9,660
  • 17
  • 77
  • 128

2 Answers2

2

If you take a look at the docs about React.createContext

The defaultValue argument is only used when a component does not have a matching Provider above it in the tree. This can be helpful for testing components in isolation without wrapping them. Note: passing undefined as a Provider value does not cause consuming components to use defaultValue.

So you don't need to pass anything to the context, but you are getting the error because of typescript.

You need to create an interface for the context value.

You are passing the context as

value={{
    state,
    dispatch
}}

But you need to have an interface for that when create the context.

export const AuthContext = React.createContext(); // <---- missing interface

Here is an example on how to do it.


Where is the "dispatch" magically coming from?

This is how useContext works.

The value you get form useContext(MyContex) is the value passed by MyContext.Provider by value prop.

e.g.

<MyContext.Provider value={fooValue}>
    {/* ... */}
</MyContext.Provider>

// same fooValue from the Provider
const fooValue = useState(MyContext)

Edit

After the async call inside onSubmit you must call dispatch and pass {type: 'LOGIN', payload: DATA_FROM_API} so it does in the reducer method and sets isAuthenticated to true

const onSubmit = (data: any) => {
  axios.post('http://localhost/api/v1/users/login/', data)
    .then(res => {        
      console.log(res.data) // <= if this is `payload`, than pass it to dispatch
      dispatch({type: 'LOGIN', payload: res.data})
    })
}
Vencovsky
  • 28,550
  • 17
  • 109
  • 176
  • I appreciate the assistance. Unfortunately, still not clear to me, how to update the context from the login component. That useContext link in React docs, I've read it a million times. It doesn't make any sense to me. – KhoPhi Jan 30 '20 at 17:12
  • @KhoPhi Please be more clear on `how to update the context from the login component`. What you want to update? What you want to do? This isn't clear for me. – Vencovsky Jan 30 '20 at 17:21
  • I've updated the question with extra details what I wanna do – KhoPhi Jan 30 '20 at 17:22
  • @KhoPhi I've updated my answer, please check it out – Vencovsky Jan 30 '20 at 17:28
0

I had a similar problem, but after many trials and errors I was able to fix it.
I was following this tutorial:
Link: https://soshace.com/react-user-login-authentication-using-usecontext-and-usereducer/
Code: https://github.com/nero2009/login-auth-useContext
The problem about routing is also covered there. But it is in JS, so you have to make some modifications. Here is what I did:

First, I specified the action types, in your example it would be:

export type AuthenticationAction = { type: 'LOGIN', payload: IPayload } | {type: 'LOGOUT'}

I specified an interface for my payload, but it may work with "any" as well.

Then, I specified the default values (this is probably what you were looking for):

const AuthenticationStateContext = React.createContext(initialState)
const AuthenticationDispatchContext = React.createContext({} as Dispatch<AuthenticationAction>)

After that it should work.

Domino
  • 124
  • 5