简体   繁体   中英

react js user gets redirected to another page on refresh

I have a react js app with react-router-dom v6 to handle the routes. The routes functionality worked just fine before i added firebase firestore, but now for some reason when i'm in the seeker page and i reload it, the home page gets rendered. It's not a problem with user authentication because that's handled by the login component, but i couldn't find the problem in my configuration.

This is my app component.

import React, {useEffect, useState} from "react";
import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom";
import { createBrowserHistory } from 'history';
import ReactDOM from "react-dom";
import "./styles/style.scss";
import Home from "./pages/home";
import Seeker from "./pages/seeker";
import NotFound from "./pages/notFound";
import Login from "./pages/login";


const App = () => {
    const [token, setToken] = useState(false);

  useEffect(() => {
      if(localStorage.getItem("token") === null){
        setToken(false);
      } else {
        setToken(true);
      }
  }, [])

    return (
        <BrowserRouter history={createBrowserHistory}> 
        <Routes>
            <Route exact path="/" element={<Login isAuth={token}/>}/>
            <Route exact path="/home" element={token ? <Home /> : <Navigate to="/"/>}/> 
            <Route exact path="/seeker" element={token ? <Seeker /> : <Navigate to="/"/>}/> 
            <Route path="*" element={<NotFound />}/>
        </Routes>   
    </BrowserRouter>
) 
}
 

ReactDOM.render(
        <App />,
    document.getElementById("root"))



The login component sets the token on localstorage correctly and the routes use it to render conditionally the components. But when i'm in the "/seeker" url the refresh takes me to "/home".

import React, { useState, useEffect } from "react";
import { Formik, Form, Field, ErrorMessage } from 'formik';
import axios from "axios";
require("babel-core/register");
require("babel-polyfill");
import GridRender from "../components/gridRender";
import { NavBar } from "../components/navBar";

async function getHeros(name) {
    return await axios.get(
        `https://www.superheroapi.com/api.php/${name}`
    );
} 

const Seeker = () => {    
    
    const [searchedHero, setSearchedHero] = useState("")
    const [matchedHeros, setMatchedHeros] = useState([]); 


    useEffect(() => {
        let isMounted = true; 
        if (isMounted) {
        getHeros(searchedHero).then((heros) => {
            const hero = heros.data.response != "error" ? heros.data.results : []
            localStorage.setItem("addHeroAction", "true");
            setMatchedHeros(hero);
        }, console.error);
    }
    return () => { isMounted = false };
    }, [searchedHero]);

    return (
        <div>
            <NavBar />
            <div className="seeker">
                <h1 className="title">Find your next teammate!</h1>
                <Formik /> //// here is a large form that i erased so it doesn't get long
                {matchedHeros.length != 0 && <GridRender matchedHeros = {matchedHeros}/>}
            </div>
        </div>
        )
    } 


export default Seeker; 

And here is my firebase config (i use version 9) in case it's important because the bug appeared after firebase was implemented.

import { initializeApp } from "firebase/app";
import "regenerator-runtime/runtime";
import { addDoc, deleteDoc, doc } from 'firebase/firestore';
import { getFirestore, collection } from 'firebase/firestore'


const firebaseConfig = {
  apiKey: "xxxxxxxxxxxxxxxxxx",
  authDomain: "xxxxxxxxxxxxxxxxxxxx",
  databaseURL: "xxxxxxxxxxxxxxxxxxxxx",
  projectId: "superteam-maker",
  storageBucket: "xxxxxxxxxxxxxxx",
  messagingSenderId: "xxxxxxxxxxxxx",
  appId: "xxxxxxxxxxxxxxxxxxxxxxxxxx"
};

initializeApp(firebaseConfig);
const db = getFirestore();
const colRef = collection(db, "team")


const addHero = (hero) => {
    addDoc(colRef, {
            name : hero.name,
            powerstats :{
                combat: parseInt(hero.powerstats.combat),
                durability: parseInt(hero.powerstats.durability), 
                intelligence: parseInt(hero.powerstats.intelligence),
                power: parseInt(hero.powerstats.power),
                speed: parseInt(hero.powerstats.speed),
                strength : parseInt(hero.powerstats.strength)
            },
            id: parseInt(hero.id),
            image: {url : hero.image.url},
            
    } )
}

const deleteHero = (hero) => {
    const docRef = doc(db, "team", hero.id);
    deleteDoc(docRef);
}


export {addHero, deleteHero, colRef}

////////UPDATE/////////

The login component autehnticates the user with a test API that returns a random token, so the user can only enter if a certain email and password get subbmited. If the auth is successfull the user gets rendered to "/home".

Here's the code fro the login component.

import React, {useEffect, useState} from "react";
import { Formik } from 'formik';
import { Navigate } from 'react-router-dom';
import axios from "axios";


const Login = (props) => {
  const [token, setToken] = useState(false);

  useEffect(() => {
      if(localStorage.getItem("token") === null){
        setToken(false);
      } else {
        setToken(true);
      }
  }, []) 


  const verification = () => {
    if( token && props.isAuth){
      return <Navigate to="/home"/>
    }
  }

    let logInError = {};
    return(
      <div className="login__background">
        <div className="login container">
          {verification()}
          <h1 className="display-1 title login__title">Make a team as <span>powerfull</span> as you!</h1>
          <h5 className="login-title__sub">Please log in to create your superteam</h5>
            <Formik
            initialValues={{ email: '', password: '' }}
            validate={values => {
            const errors = {};
            
            if (!values.email) {
              errors.email = 'Required';
            } else if (
              !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i.test(values.email)
            ) {
              errors.email = 'Invalid email address';
            } 
            
            if (!values.password){
                errors.password = "Required";
            } 
            return errors;
            }}
            onSubmit={(values) => {
                axios.post("http://challenge-react.alkemy.org/", values)
                .then(response => {
                    let token = response.data.token
                    localStorage.setItem("token", token);
                    localStorage.setItem("addHeroAction", false);
                    setToken(true);
                    return <Navigate to="/home"/>
                })
                .catch(error => {
                    if (error.response) {
                        logInError = error.response.data.error;
                        window.alert(logInError);
                      } else if (error.request) {
                        console.log(error.request);
                      } else {
                        console.log('Error', error.message);
                      }
                      console.log(error.config);
                })
            }}>

            {({
              values,
              errors,
              touched,
              handleChange,
              handleBlur,
              handleSubmit
            }) => (
            <form onSubmit={handleSubmit}>
                <label className="form-label label" name="email">Email</label>
                <br />
              <input
                className="form-control form-control-lg input"
                type="email"
                name="email"
                onChange={handleChange}
                onBlur={handleBlur}
                value={values.email}
              />
              {errors.email && touched.email && errors.email}
              <br />
              <label className="form-label label " name="password">Password</label>
              <br />
              <input
                className="form-control form-control-lg input"
                type="password"
                name="password"
                onChange={handleChange}
                onBlur={handleBlur}
                value={values.password}
              />
              {errors.password && touched.password && errors.password}
              <br />
              <button type="submit" className="btn btn-danger">
                Submit
              </button>
            </form>
            )}
            </Formik>
            
        </div>
      </div>
    )

}

export default Login;

It also gets an IsAuth propr from App.js in case it's the first time the user enters the page and there is no token saved.

The issue I see is the token state being initially false. The useEffect runs at the end of the render cycle, so for the initial render token is false

const [token, setToken] = useState(false);

useEffect(() => {
  if (localStorage.getItem("token") === null) {
    setToken(false);
  } else {
    setToken(true);
  }
}, []);

and any Route being rendered using the token state will use this initial false value and act accordingly.

<Route path="/seeker" element={token ? <Seeker /> : <Navigate to="/" />} /> 

If on the "/seeker" path and the token value is false , the Navigate component is rendered.

Assuming all your authentication flow is correct and working and sets the token value into localStorage then you should initialize the token state from localStorage.

const initializeState = () => !!JSON.parse(localStorage.getItem("token"));

const [token, setToken] = useState(initializeState);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM