React not writing all values from localStorage to useState - reactjs

I have an application in which you can register and log in. I store all users in useState, which is located in App.js (initially there are three users). If a new user registers, then I store his data in localStorage, so that when the application is restarted, they would be in my UseState App.js, this is the problem.
If you work in one session, then everything works well: After registrations, I can easily log into a new account, regardless of how many I have created (Data is written correctly in localStorage), but as soon as I restart the application, then in my App.js from localStorage only one LAST object from localStorage comes in, although if I display the actions of my useEffect in the console, then
From localStorage, all users are moved one by one, and as a result, only the last user is added to useState, can you tell me what could be the problem?
Here code:
App.js (Error in useEffect i think)
import React, {useState, useEffect} from 'react';
import './App.css';
import {Routes, Route} from 'react-router-dom'
import Registr from './components/AuthPage/Registr';
import Login from './components/AuthPage/Login';
function App() {
const [activeUser, setActiveUser] = useState();
const [realUsers, setRealUsers] = useState([]);
const [users, setUsers] = useState(
[
{
id: 0,
name: 'Andrey',
email: 'qwerty#mail.ru',
password: 'qwerty'
},
{
id: 1,
name: 'Roma',
email: 'ramenCisco#mail.ru',
password: '123'
},
{
id: 2,
name: 'Ilya',
email: 'ilyazxc#mail.ru',
password: 'zxc'
}
]
)
useEffect(() => {
for (let i = 0; i < localStorage.length; i++) { //
const userKey = localStorage.key(i); //find user key name
const JSONUserFromLocalStorage = localStorage.getItem(userKey);
const parsedUser = JSON.parse(JSONUserFromLocalStorage);
setUsers([...users, parsedUser])
}
}, []);
return (
<>
<Routes>
<Route path = '/' element = {<Login users = {users} setActiveUser = {setActiveUser}/>}></Route>
<Route path = '/registration' element = {<Registr users = {users} setUsers = {setUsers}/>}></Route>
</Routes>
</>
);
}
export default App;
Login.js
import React, {useReducer, useState} from 'react';
import closedEye from '../icons/closedEye.png';
import openedEye from '../icons/openedEye.png';
import warning from '../icons/warning.png';
import './Login.css';
import {Link} from 'react-router-dom';
function Login({users, setActiveUser}){
const [anError, setStatus] = useState()
const [isPrivate, setPrivate] = useState(true);
let typeOfEye = isPrivate ? closedEye : openedEye;
let typeOfInput = isPrivate ? 'password' : 'text';
const [form, setForm] = useState({
email: '',
password: ''
});
const changeHandler = (e) => {
setForm({...form, [e.target.name] : e.target.value});
}
const checkForValidate = () =>{
const {email, password} = form;
if (email.length === 0){
setStatus(
<div className='error-block'><img src = {warning} alt = 'warning'></img><p className='error'>Enter your email</p></div>
);
return
}else{
setStatus()
}
if (password.length === 0){
setStatus(
<div className='error-block'><img src = {warning} alt = 'warning'></img><p className='error'>Enter your password</p></div>
);
return
}else{
setStatus()
}
//Checking if a user exists in the database by email
const doesEmailValid = users.find(user => user.email === email)
if(!doesEmailValid){ //doesEmailValid the user object we found by email setStatus(
<div className='error-block'><img src = {warning} alt = 'warning'></img><p className='error'>Uncorrect email</p></div>
);
return
}else{
setStatus()
}
if (doesEmailValid.password !== password){
setStatus(
<div className='error-block'><img src = {warning} alt = 'warning'></img><p className='error'>Uncorrect password</p></div>
);
}else{ //If everything is alright:
setStatus(<p className='succes'>User successfully authorized</p>);
setActiveUser(doesEmailValid);
}
}
return (
<div className='login-wrapper'>
<div className = 'login'>
<h3>authorize</h3>
{anError}
<form
onSubmit={(e) => e.preventDefault()}>
<div className='form'>
<div className='inputs'>
<div className='email-field'>
<label htmlFor='email'>Email</label>
<input
type = 'email'
name = 'email'
onChange = {changeHandler}
>
</input>
</div>
<div className='password-field'>
<label htmlFor='email'>Password</label>
<div className='row'>
<input
type = {typeOfInput}
name = 'password'
maxLength={14}
onChange = {changeHandler}
>
</input>
{/* <img src = {require('./closedEye.png')}></img> */}
<img
src = {typeOfEye} alt = 'eye'
onClick={()=> {setPrivate(!isPrivate)}}
></img>
</div>
</div>
</div>
<div className='login-buttons'>
<button
type = 'submit'
onClick={checkForValidate}
>Enter</button>
<Link to = '/registration'>No account?</Link>
</div>
</div>
</form>
</div>
</div>
)
};
export default Login;
If we created one account in the LAST session, then authorization is successful.
If we have created several accounts, then we can only log in to the LAST created one, otherwise my error comes out: Uncorrect email

Quickly looking at your code it seems that setUsers([...users, parsedUser]) is going to be an issue. React does batch setState updates every 10ms or so, which means that your entire loop will have executed over and over before it ever actually sets the state. That's why it will only set the last item that was ever called before the state update happens.
Instead do something like:
users.push(JSON.parse(// user))
Then:
setUsers(users)
So you can set all of them at once.

This doesn't answer your question, but I want to call out a security issue with your code. It looks like you're storing passwords in the database, which you should never do. You also shouldn't store them in local storage.
You want to store a password hash in the database, and hash the password every time on login to check if it's the correct password. See here for some more info.

Related

Uncaught (in promise) FirebaseError: Invalid document reference

import { getDocs, collection, query, doc, addDoc } from "firebase/firestore/lite";
import { useState } from "react";
import { db } from "../firebaseConfig";
import { useEffect } from "react";
function EndGame(startGame){
const {startGameHandler} = startGame;
const startGameClick = startGameHandler[0];
const time = startGameHandler[1];
const [leaderboard, setLeaderboard] = useState([]);
const [user, setUser] = useState("");
const [username, setUsername] = useState("")
const [isAnonymous, setIsAnonymous] = useState(false);
const loginAnonymously = () =>{
console.log("login hivas ", user)
setUser(username)
setIsAnonymous(true)
}
const setScore= async(timeprop, userprop)=>{
console.log(time, user)
await addDoc(doc(db, "Leaderboard"), {
name: userprop,
time: timeprop,
})
}
async function getLeaderboard(){
const q = query(collection(db, "Leaderboard"));
const chacSnapShot = await getDocs(q);
const char = chacSnapShot.docs.map(doc => doc.data());
setLeaderboard(char)
}
useEffect(()=>{
setScore(time, user)
getLeaderboard()
}, [isAnonymous])
return(
<div className={`endgame-page`}>
{!isAnonymous && (
<div className="endgame-div">
<input
type="text"
placeholder="Enter a username"
value={username}
onChange={e => setUsername(e.target.value)}
/>
<button onClick={loginAnonymously}>Login Anonymously</button>
</div>
)}
{isAnonymous && (
<div className="endgame-div">
<h1 className="endgame-heading">Leaderboard</h1>
<div className="endgame-leaderboard">
{leaderboard.map((data)=>{
return(
<div key={data.name} className="user-container">
<p className="username">{data.name}</p>
<p className="userdata">{data.time}</p>
</div>
)
})}
</div>
<button className="endgame-button" onClick={startGameClick} >Start Game</button>
</div>
)}
</div>
)
}
export default EndGame
So I have this endgame component and when it renders for some reasons the setScore function gets called and i think thats why i get a error of this:
Uncaught (in promise) FirebaseError: Invalid document reference. Document references must have an even number of segments, but Leaderboard has 1.
On line 27. Am i in the wrong here thinking its beacause of setScore gets called on render? If not what the problem/solution?
In firebase i have a Leaderboard collection inside that i want to create docs from users time and name, (each users should have 1 doc)
Method addDoc should have a collection reference not a document reference. You'd only use a document reference if you want to specify a document name into it and it should use setDoc instead, see sample code below:
const setScore= async(timeprop, userprop)=>{
console.log(time, user)
await setDoc(doc(db, "Leaderboard", "<document-name>"), {
name: userprop,
time: timeprop,
})
}
To correct this, check out the sample code below:
const setScore= async(timeprop, userprop)=>{
console.log(time, user)
await addDoc(collection(db, "Leaderboard"), {
name: userprop,
time: timeprop,
})
}
You may want to checkout this documentation for more information.

Adding Local Storage option in my ToDo List site

I made my basic ToDo list site after learning react. I wanted to add a function of saving the items on reload. I am a beginner in react so I am facing difficulty in this. I tried the following code:
import React from "react";
import ToDoList from "./components/ToDoList";
import Navbar from './components/Navbar'
import '../src/App.css'
export default function TodoInput() {
const saveLocalTasks = () => {
let savedTasks = localStorage.getItem('tasks')
console.log(savedTasks)
if (savedTasks) {
return JSON.parse(localStorage.getItem('tasks'))
} else {
return []
}
}
const [task, setTask] = React.useState('')
const [count, setCount] = React.useState(0)
const [taskList, setTaskList] = React.useState([saveLocalTasks()])
const [disable, setDisable] = React.useState(true)
const [viewTaskList, setViewTaskList] = React.useState(true)
const updateTaskList = () => {
setTaskList([...taskList, {object: task, key: Date.now()}])
setTask('')
setViewTaskList(false)
setCount(count + 1)
setDisable(true)
}
const inputValue = e => {
setTask(e.target.value)
e.target.value === '' || task === '' || task.length === 0
?
setDisable(true)
:
setDisable(false)
}
// console.log(task.length)
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList.object))
}, [taskList])
return (
<div>
<Navbar />
<header>
<div className="todolist-border">
<div className="todo-input-form">
<input
className = "inputText"
placeholder="Add a Task"
value={task}
onChange = {inputValue}
/>
<button disabled = {disable} onClick = {updateTaskList} className="todo-add-button">+</button>
</div>
{
viewTaskList || count === 0
?
<div className="pendingTasks-div">
<img className = "pending-task-image"
src= "https://dm0qx8t0i9gc9.cloudfront.net/watermarks/image/rDtN98Qoishumwih/task-pending-cartoon-business-vector-illustrations_zJCs81OO_SB_PM.jpg"
alt="pending-tasks" />
<p className="no-task-message">There are no pending tasks!! #Enjoy🥳🥳</p>
</div>
:
<ToDoList count = {count} setCount = {setCount} task = {task} taskList = {taskList} setTaskList = {setTaskList}/>
}
</div>
</header>
</div>
)
}
But the following error is coming up:
The following is the code for ToDoList component:
import React from "react";
export default function ToDoList(props) {
const deleteTaskListItem = (key) => {
const updatedList = props.taskList.filter((item) => {
return (
item.key !== key
)
})
props.setTaskList(updatedList)
props.setCount(props.count - 1)
}
return(
<div>
{props.taskList.map((item) => {
return (
<div key = {item.key} className="todolist-div">
<input type="checkbox" className="list-checkbox">
</input>
<p>{item.object}</p>
<button onClick={()=>deleteTaskListItem(item.key)} className="delete-button">X</button>
</div>
)
})}
</div>
)
}
Kindly suggest a method to add this feature.
The above error happens when you try to JSON.parse undefined. Check this link. Here, I tried to do some changes in your code on CodeSandbox. There you can find some changes I have made.
Firstly, you shouldn't try to set data in this useState const [taskList, setTaskList] = React.useState([saveLocalTasks()]). You should set data in useEffect.
In the following code, you are trying to save taskList.object but taskList is an array. The below code will throw an error.
React.useEffect(() => {
localStorage.setItem('tasks', JSON.stringify(taskList.object))
}, [taskList])
As you asked in your question, you want to try to save data when the user reloads the window. You can achieve this by using window.onbeforeunload event (line 48).
Hope the above will help you.
P.S: The codesandbox code I shared isn't fully functional. I have made just some changes that will help you to go ahead with your coding. Thank you.

Array in a react state is not updating correctly

I basically try to update filter the items from the all locations array to dropDownLocation array, but it is not updating correctly. on the first change in input field it wrongly update the array and the second change it does not update it.
import logo from "./logo.svg";
import "./App.css";
import { useState, useEffect } from "react";
function App() {
// location entered by the user
const [location, setlocation] = useState("");
const [allLocations, setallLocations] = useState(["F-1", "F-2", "G-1", "G-2"]);
const [dropDownLocations, setdropDownLocations] = useState([]);
const filterLocations = (userInput) => {
console.log("user input ", location);
allLocations.map((i) => {
if (i.includes(location)) {
console.log("true at ", i);
setdropDownLocations([...dropDownLocations, i]);
} else {
setdropDownLocations([]);
}
});
console.log("after map ", dropDownLocations);
};
return (
<div className="App">
<div>
<input
value={location}
onChange={(e) => {
setlocation(e.target.value);
filterLocations(e.target.value);
}}
/>
<ul>
{dropDownLocations.map((i) => (
<li key={i}>{i}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
You don't need to make that complicated, Just filter the array based on the user's input
const filterLocations = (userInput) => {
setdropDownLocations(
allLocations.filter((location) => location.includes(userInput))
);
};
I made it simpler for you in this working example:
The setState is an asynchronous function and so your current implementation isn't working properly as you are trying to read the state before it is updated.
Update your filterLocations function like following:
const filterLocations = (e) => {
const location = e.target.value;
const filteredLocation = allLocations.filter(i => i.includes(location));
setlocation(location);
setdropDownLocations(filteredLocation)
};
And update your input tag like following:
<input value={location} onChange={filterLocations} />
It is not working because for each location, you are setting dropdown location, and if it doesn't contain the location, you set it to empty array [] again.
allLocations.map((i) => {
if (i.includes(location)) {
console.log("true at ", i);
setdropDownLocations([...dropDownLocations, i]);
} else {
setdropDownLocations([]);
}
});
A better approach would be:
setDropDownLocation([...allLocations].filter((i) => i.includes(userInput))
There is some mistakes what you have done, I have made some changes try to run the code which I have written.
import logo from "./logo.svg";
import "./App.css";
import { useState, useEffect } from "react";
const ALL_LOCATIONS = ['F-1', 'F-2', 'G-1', 'G-2'];
function App() {
// location entered by the user
const [location, setLocation] = useState("");
const [dropDownLocations, setDropDownLocations] = useState([]);
function onLocationInputChange(event){
setLocation(event.target.value);
setDropDownLocations(ALL_LOCATIONS.filter((item)=>item.includes(event.target.value)))
}
return (
<div className="App">
<div>
<input value={location} onChange={onLocationInputChange} />
<ul>
{dropDownLocations.map((loc) => (
<li key={`${location}-${loc}`}>{loc}</li>
))}
</ul>
</div>
</div>
);
}
export default App;
This is caused by the fact that your onChange handler is defined right in the JSX, causing React to recreate a new function at every render (same goes for filterLocations one).
You should always try to extract every single piece of JS logic outside of the component, or at least memoize them, here's how:
import React, { useState, useCallback } from "react";
import logo from "./logo.svg";
import "./App.css";
const ALL_LOCATIONS = ['F-1', 'F-2', 'G-1', 'G-2'];
function App() {
// location entered by the user
const [location, setLocation] = useState("");
// locations shown to the user in dropdown (filterable)
const [dropDownLocations, setDropDownLocations] = useState([]);
const onLocationInputChange = useCallback(
(ev) => {
// In case no target passed to callback, do nothing
if (!ev || !ev.target || !ev.target.value) {
return;
}
const userInput = ev.target.value;
// Filter so that if user input matches part of the location
// it gets not filtered out
setDropDownLocations([
...ALL_LOCATIONS.filter(
(loc) =>
loc.startsWith(userInput) ||
loc.endsWith(userInput) ||
loc.indexOf(userInput) !== -1
),
]);
// Finally update the location var
setLocation(userInput);
},
[setDropDownLocations]
);
return (
<div className="App">
<div>
<input value={location} onChange={onLocationInputChange} />
<ul>
{dropDownLocations.map((loc) => (
<li key={`${location}-${loc}`}>{loc}</li>
))}
</ul>
</div>
</div>
);
}
export default App;

how to prevent multiple re-render in react

import { useSelector, useDispatch } from "react-redux";
import { useEffect, useState } from "react";
import { useHistory } from "react-router-dom";
import { IoMdArrowRoundBack } from "react-icons/io";
import axios from "axios";
import { fetchMovies } from "../../feautures/movies/moviesSlice";
import Rating from "../../components/UI/Rating/Rating";
import request from "../../requests";
import "./SingleMoviePage.scss";
import SimilarMovies from "../../components/SimilarMovies/SimilarMovies";
const SingleMoviePage = ({ match }) => {
const dispatch = useDispatch();
const [movieDetails, setMovieDetails] = useState({});
const [movieCredits, setMovieCredits] = useState({});
const history = useHistory();
console.log("single rendered")
// number month to string
const date = new Date(movieDetails.release_date);
const dateWithMonthName =
date.getFullYear() +
"-" +
date.toLocaleString("en-EN", { month: "long" }) +
"-" +
date.getDay();
/* params */
const movieId = match.params.id;
const page = match.params.page;
const genre = match.params.genre;
/* movies reducer handle */
const moviesStatus = useSelector((state) => state.movies.status);
/* base urls */
const baseImgUrl = "https://image.tmdb.org/t/p/original";
const movieDetailUrl = `https://api.themoviedb.org/3/movie/${movieId}?api_key=c057c067b76238e7a64d3ba8de37076e&language=en-US`;
const movieCastUrl = `https://api.themoviedb.org/3/movie/${movieId}/credits?api_key=c057c067b76238e7a64d3ba8de37076e&language=en-US`;
// go home page
const goHOme = () => {
history.goBack()
};
// fetch movie cast
useEffect(() => {
const fetchMovieCast = async () => {
let response = await axios.get(movieCastUrl);
response = response.data;
setMovieCredits(response);
};
fetchMovieCast();
}, [movieCastUrl]);
// fetch movie details
useEffect(() => {
const fetchMovieDetails = async () => {
let response = await axios.get(movieDetailUrl);
response = response.data;
setMovieDetails(response);
};
fetchMovieDetails();
}, [movieDetailUrl]);
let content;
if (moviesStatus === "loading") {
} else if (moviesStatus === "succeeded") {
content = (
<div
className="single-movie__container"
style={{
backgroundImage: `url(${
movieDetails.backdrop_path
? baseImgUrl + movieDetails.backdrop_path
: baseImgUrl + movieDetails.poster_path
})`,
}}
>
<div className="single-movie__details">
<IoMdArrowRoundBack
className="single-movie__back"
onClick={goHOme}
size={65}
color={"#e50914"}
/>
<h1 className="single-movie__title">{movieDetails.title}</h1>
<div className="single-movie__rate">
<Rating
rating={movieDetails.vote_average}
className="single-movie__stars"
/>
</div>
<p className="single-movie__overview">{movieDetails.overview}</p>
<div className="single-movie__informations single-movie__informations--genres">
<label className="single-movie__informations-heading">Genres</label>
<div className="single-movie__informations-container">
{movieDetails.genres?.map((genre) => {
return <div className="single-movie__info">{genre.name}</div>;
})}
</div>
</div>
<div className="single-movie__informations single-movie__informations--stars">
<label className="single-movie__informations-heading">
Starring
</label>
<div className="single-movie__informations-container">
{movieCredits.cast?.slice(0, 4).map((star) => {
return <div className="single-movie__info">{star.name}</div>;
})}
</div>
</div>
<div className="single-movie__informations single-movie__informations--released">
<label className="single-movie__informations-heading">
Release Date
</label>
<div className="single-movie__informations-container">
<div className="single-movie__info">{dateWithMonthName}</div>
</div>
</div>
<div className="single-movie__informations single-movie__informations--production">
<label className="single-movie__informations-heading">
Production
</label>
<div className="single-movie__informations-container">
{movieDetails.production_countries?.slice(0, 2).map((country) => {
return <div className="single-movie__info">{country.name}</div>;
})}
</div>
</div>
</div>
<SimilarMovies movieId={movieId} />
</div>
);
}
useEffect(() => {
if (genre === "POPULAR") {
dispatch(fetchMovies(request.fetchPopular(page)));
} else if (genre === "NOW PLAYING") {
dispatch(fetchMovies(request.fetchNowPlaying(page)));
} else if (genre === "UP COMING") {
dispatch(fetchMovies(request.fetchUpComing(page)));
}
}, [dispatch, genre, page]);
return <div className="single-movie">{content}</div>;
};
export default SingleMoviePage;
Hi all.When i clicked Card compenent it navigate me to the SingleMoviePage component.But SingleMoviePage component re-render five times.How can i find this issues's source ? And how can i prevent that ? Finally is there any problem to fetch MovieCast and MovieDetails in same useEffect hook ?
github repo : https://github.com/UmutPalabiyik/hope-movie-app
demo : https://hope-movie.web.app/page/1
The first 2 useEffect hooks fetch data separately which then update your local states, which then triggers re-rendering.
If you don't want to re-render after each data fetch (state update), I'd suggest adding a loading state. Set it to true first and return a loading icon as your content. Then after both movieDetails and movieCredits have been populated, set it to false and return the actual content. This should render twice in total.
Have you considered graphql? GraphQL can combine your api calls into one and it also handles loading state and error state.
Whatever solution you have, re-rendering will happen when you are fetching data. Initial render won't have any data and after fetching data it must re-render.
You should use only one useEffect hook your code is running for all three. React will handle the rest itself.

Issue in getting data from API in React

So i've basically got 2 components on my page.
First is the search component where the users need to type their username and second one where their stats get displayed
and here is my API request call in App.js
useEffect(()=>{
const fetchStats = async ()=> {
const result = await axios.get(`https://cors-anywhere.herokuapp.com/https://public-api.tracker.gg/v2/csgo/standard/profile/steam/${username}`,
{
headers: {
'TRN-Api-Key' : '***************************',
}
}
)
if(username !== null){
console.log(result.data)
setStats(result.data)
}
}
fetchStats()
},[username])
and this is the search component
const Search = ({setInputText, setUsername, inputText, username}) => {
const inputHandler = (e)=> {
setInputText(e.target.value)
}
const searchHandler = (e)=> {
e.preventDefault()
setUsername(inputText)
}
return (
<div>
<form>
<input value={inputText} onChange={inputHandler} type="text"/>
<button onClick={searchHandler}>Search</button>
</form>
</div>
)
}
What i'm having an issue with is when i click a button in the username component the value(username) from the form gets stored in the 'username' state in App.js. Now i'm using this code in the stats component.
const Stats = ({stats}) => {
return (
<div>
<h1>{stats.data.platformInfo.platformUserHandle}</h1>
</div>
)
}
export default Stats
Here stats.data.platformInfo.platformUserHandle doesn't exist when the app starts so it gives me a LOT of errors. How do i keep the app from crashing till the user has input something and data can be sent to the stats component?

Resources