Infinite loop when working with react and react-firebase-hooks - javascript

I am working with a navigation bar that should be able to switch between multiple chat rooms using react and react-firebase-hooks. (https://github.com/CSFrequency/react-firebase-hooks)
However, the chat room will infinitely re-render itself when I choose a room in nav-bar.
I initially thought this is a router issue, but having each rooms sharing the same url, the issue persists.
Right now, when I choose a room using the nav bar, it will send that room number back to App.js using a callback function. App.js will pass that room number to ChatRoom.js, which will get the data from firestore, and re-render itself.
I struggled for several days trying to find anything that could cause the infinite loop with minimal success. Any help would be appreciated!
ChatRoom.js
import React, { useMemo, useRef, useState } from 'react';
import { withRouter } from 'react-router';
import { useCollectionData, useDocument, useDocumentData } from 'react-firebase-hooks/firestore';
import firebase, { firestore, auth } from '../Firebase.js';
import ChatMessage from './ChatMessage';
const ChatRoom2 = (props) => {
console.log("chat room rendered");
function saveQuery(){
const channelid= props.channelid;
const messagesRef = firestore.collection('messages').doc(channelid).collection('chats');
const query = messagesRef.orderBy('createdAt').limitToLast(25);
return [messagesRef,query];
}
var returnedVal = useMemo(()=>saveQuery , [props.channelid]);
const messagesRef = returnedVal[0];
const query = returnedVal[1];
const [messages] = useCollectionData(query, { idField: 'id' });
const [formValue, setFormValue] = useState('');
const sendMessage = async (e) => {
e.preventDefault();
console.log(messagesRef);
console.log(query);
console.log(messages);
const { uid, photoURL } = auth.currentUser;
await messagesRef.add({
text: formValue,
createdAt: firebase.firestore.FieldValue.serverTimestamp(),
uid,
photoURL
})
setFormValue('');
}
return (<>
<main>
{messages && messages.map(msg => <ChatMessage key={msg.id} message={msg} />)}
</main>
<form onSubmit={sendMessage}>
<input value={formValue} onChange={(e) => setFormValue(e.target.value)} placeholder="say something nice" />
<button type="submit" disabled={!formValue}>🕊️</button>
</form>
</>)
}
export default ChatRoom2;
ChatList.js (nav bar)
const ChatList = (props) => {
console.log("list rendered");
const query = firestore.collection('users').doc(auth.currentUser.uid).collection('strangers').orderBy('channelID').limitToLast(10);
//console.log(query);
const [channelidArr] = useCollectionData(query, { idField: 'id' });
return (
<div>
{channelidArr && channelidArr.map(channelid =>
<div>
<button onClick={() => props.parentCallback(channelid.channelID)}>{channelid.channelID}</button>
<br />
</div>)}
</div>
);
};
export default ChatList;
App.js
import React, { useRef, useState } from 'react';
import {
BrowserRouter,
Switch,
Route,
Link
} from "react-router-dom";
//import './App.css';
import firebase, { firestore, auth } from './Firebase.js';
import { useAuthState } from 'react-firebase-hooks/auth';
import { useCollectionData } from 'react-firebase-hooks/firestore';
import ChatList from './components/ChatList.js';
import FindNew from './components/FindNew.js';
import Footer from './components/Footer.js';
import Profile from './components/Profile.js';
import ChatRoom2 from './components/ChatRoom2.js';
import SignOut from './components/SignOut.js';
import SignIn from './components/SignIn.js';
import SignUp from './components/SignUp.js';
import ChatRoom from './components/ChatRoom.js';
function App() {
console.log('App rendered');
const [user] = useAuthState(auth);
const [roomNum, setRoomNum] = useState([]);
const callbackFunction = (childData) => {
setRoomNum(childData);
};
return (
<div className="App">
<header>
<h1>⚛️🔥💬</h1>
<SignOut auth={auth} />
</header>
<BrowserRouter >
<Footer />
<Switch>
<Route path="/profile">
<Profile />
</Route>
<Route path="/new">
<FindNew />
</Route>
<Route path="/signup">
{() => {
if (!user) {
return <SignUp />;
} else {
return null;
}
}}
</Route>
<Route path="/direct">
{user ?
<div>
<ChatList parentCallback={callbackFunction} />
<ChatRoom2 channelid={roomNum} />
</div> : <SignIn />}
</Route>
</Switch>
</BrowserRouter>
</div>
);
};
export default App;

Issue Summary
useCollectionData memoizes on the query parameter but since a new query reference was declared each render cycle the firebase hook was rerun and updated collection and rerendered the component.
const { channelid } = props;
const messagesRef = firestore
.collection('messages')
.doc(channelid)
.collection('chats');
const query = messagesRef // <-- new query reference
.orderBy('createdAt')
.limitToLast(25);
const [messages] = useCollectionData(
query, // <-- reference update trigger hook
{ idField: 'id' },
);
Solution
query has only a dependency on the channelid prop value, so we can memoize the query value and pass a stable value reference to the useCollectionData hook.
const { channelid } = props;
const query = useMemo(() => {
const messagesRef = firestore
.collection('messages')
.doc(channelid)
.collection('chats');
const query = messagesRef.orderBy('createdAt').limitToLast(25);
return query;
}, [channelid]);
const [messages] = useCollectionData(
query, // <-- stable reference
{ idField: 'id' },
);

Related

useContext() returns null value

I am trying to use useContext to get the current logged in user details. I am getting an error in my console which says:
Uncaught TypeError: user is null
children Animate.jsx:79
Animate Animate.jsx:76
I am getting this error whenever I Logout. I am facing no errors in the console when I am logged in.
Here is the code:
Context.js File:
import { onAuthStateChanged } from "firebase/auth";
import { useEffect, useState } from "react";
import { createContext } from "react";
import { auth } from "./firebase";
export const Context = createContext()
const ContextProviderfunc=({children})=>{
const use={
"mail":'history'
}
const [user,setuser]=useState({});
useEffect(()=>{
const unsubscribe = onAuthStateChanged(auth,(currentUser)=>{
// console.log(currentUser);
setuser(currentUser);
})
return ()=>{
unsubscribe();
}
},[])
return(
<Context.Provider value={user}>
{children}
</Context.Provider>
)
}
export default ContextProviderfunc
Animate.jsx File:
import React from 'react'
import {
BrowserRouter as Router, Routes, Route, useLocation, Link
} from 'react-router-dom'
import Navbar from './Navbar'
import RecipeCard from './RecipeCard'
import Details from './Details'
import { AnimatePresence, motion } from "framer-motion"
import Add from './Add'
import clicked from './RecipeCard'
import { useState } from 'react'
import Login from './Login'
import Register from './Register'
import Protected from './Protected'
import Logout from './Logout'
import Myrecipe from './Myrecipe'
import { useContext } from 'react'
import { Context } from '../Context'
export default function Animate(props) {
const user=useContext(Context)
const [pname, setpname] = useState()
function assign(nigga) {
setpname(nigga);
console.log(nigga);
}
const location = useLocation()
return (
<motion.div className='all'
>
<AnimatePresence>
<Routes location={location} key={location.pathname}>
<Route path='/Login' element={<Login />} />
<Route path='/Register' element={<Register />} />
<Route path='*' element={[<Protected><Navbar /></Protected>, <h2>Recipes</h2>,
<div className='recipes'>
{props.query ? props.query.map((object, i) => (
<Link className='lin' to='/Details'>
<div onClick={() => assign(object.Name)}>
<Protected><RecipeCard
src={object.src}
name={object.Name}
ingredients={object.Ingredients}
steps={object.Steps}
key={i}
/></Protected>
</div></Link>
)) : "Loading"}
<Link className='lin' to='/Add'>
<div className='cardcont'>
<img src='https://cdn.pixabay.com/photo/2017/11/10/05/24/add-2935429_960_720.png' alt="1" className='cardimg' />
<div className='cardbody'>
<h3>Add</h3>
</div>
</div></Link>
</div>
]} />
<Route path='/MyRecipe' element={[<Protected><Navbar /></Protected>, <h2>My Recipes</h2>,
<Protected>
<div className='recipes'>
{props.query2 ? props.query2.map((object, i) => {
if(user.email==object.email){
console.log(user.email);
return(<Link className='lin' to='/Details'>
<div onClick={() => assign(object.Name)}>
<Protected><RecipeCard
src={object.src}
name={object.Name}
ingredients={object.Ingredients}
steps={object.Steps}
key={i}
/></Protected>
</div></Link>)
}
}
) : "Loading"}
<Link className='lin' to='/Add'>
<div className='cardcont'>
<img src='https://cdn.pixabay.com/photo/2017/11/10/05/24/add-2935429_960_720.png' alt="1" className='cardimg' />
<div className='cardbody'>
<h3>Add</h3>
</div>
</div></Link>
</div>
</Protected>
]} />
<Route path='/Details' element={
[<Navbar />, props.query ? props.query.map((object, m) => {
if (pname == object.Name) {
console.log(object.Name);
return (<Details
src={object.src}
name={object.Name}
Ingredients={object.Ingredients}
Steps={object.Steps}
key={m}
/>)
}
}) : "Loading"]} />
<Route path='/Add' element={[<Navbar />, <Add />]} />
</Routes>
</AnimatePresence>
</motion.div>
)
}
line 79 of Animate.jsx
if(user.email==object.email){
console.log(user.email);
App.js file:
import Navbar from './components/Navbar';
import RecipeCard from './components/RecipeCard';
import { useContext, useEffect, useState } from 'react';
import { auth, db, storage } from './firebase'
import { collection, addDoc, getDocs, doc } from "firebase/firestore";
import { async } from '#firebase/util';
import Details from './components/Details'
import Animate from './components/Animate';
import {BrowserRouter as Router, Route,Routes} from 'react-router-dom'
import Add from './components/Add';
import ContextProviderfunc from './Context';
import { Context } from './Context';
function App() {
const [query, setquery] = useState()
const [query2, setquery2] = useState()
const [recipe, setrecipe] = useState()
const add = async (e) => {
e.preventDefault()
try {
const docRef= await addDoc(collection(db, "recipe"),
{
src: 'https://images.immediate.co.uk/production/volatile/sites/30/2017/02/Two-panna-cotta-on-plates-298e616.jpg',
ingredients: ['Dish'],
steps: ['Description']
}, {
src: 'https://imagesvc.meredithcorp.io/v3/mm/image?url=https%3A%2F%2Fstatic.onecms.io%2Fwp-content%2Fuploads%2Fsites%2F43%2F2022%2F04%2F19%2F22749-the-best-banana-pudding-mfs-366-1x1-1.jpg',
ingredients: ['Dish'],
steps: ['Description']
});
// console.log('morein');
// console.log("Document written with ID: ", docRef.id);
} catch (error) {
console.log('morein');
console.error("Error adding document: ", error);
}
}
useEffect(() => {
const getrecipe = async () => {
const data = await getDocs(collection(db, 'recipe'))
const data2 = await getDocs(collection(db, 'userrecipe'))
console.log(data);
setquery(data.docs.map((doc) => (
{
...doc.data(), id: doc.id
})
))
setquery2(data2.docs.map((doc) => (
{
...doc.data(), id: doc.id
})
))
console.log(query);
};
getrecipe();
}, []);
useEffect(() => {
// searchRecipes();
}, [])
return (
<div>
<ContextProviderfunc>
<Router>
<Animate query={query} query2={query2}/>
</Router>
</ContextProviderfunc>
</div>
);
}
export default App;
These two files I think are needed to debug. If any more code is needed I will provide it. Please find out the issue in the above code
Whenever the user logs out, value of currentUser will become null in here:
onAuthStateChanged(auth,(currentUser)=>{
// console.log(currentUser);
setuser(currentUser); // currentUser is null
})
Since you are updating the state (setuser), The Context Provider rerenders and passes down null as value.
return(
<Context.Provider value={user}> // user is null, and is being passed down
{children}
</Context.Provider>
)
Within Animate you receive the null value.
const user=useContext(Context) // user is null, after loging out.
Long story short, user is at times null within Animate, so you don't have the guarantee to use properties like email, like this line below:
if(user.email==object.email){
console.log(user.email);
The solution: Wherever you need to use user, first check if it equals to null.
If you set logged out user ti be null, you should not expect to retrieve email address of null.
You should check (read from context) if user still logged in.
For example :
if(user) {
return <div>I am logged in user and my email is {user.email} </div>
}
return <div>You are not logged In </div>
Specific to your case: check first user and then user.email to avoid Uncaught TypeError: user is null
if(user && user.email === object.email) .....
This is just a friendly suggestion, which is not related with your question: Use ===instead of == for for information you can read this page

React: can't access property "slug", _ref is undefined

This code should effect in the display of the blog content according to the slug. After modification and execution of the code the error "Unhandled Rejection (TypeError): can't access property "slug", _ref is undefined" has occurred. Can you help?
The code for BlogDetail.js
import React, { useState, useEffect } from 'react';
import axios from 'axios';
import { Link } from 'react-router-dom';
function BlogDetail() {
// blogs is the data, setBlogs is a function that sets the value of blogs
const [blogs, setBlogs] = useState([]);
useEffect(() => {
const fetchBlogs = async({slug}) => {
try {
const res = await axios.get(`http://localhost:8000/api/blog/${slug}`);
setBlogs(res.data);
} catch (err) {}
};
fetchBlogs();
}, []);
const createBlog = () => {
return {__html: blogs.content}
};
return (
<div className="container-m-3">
{blogs.map((BlogPost, slug) => (
<article key={slug}>
<Link to={`/blog/${BlogPost.slug}`} className="stretched-link">{BlogPost.title}</Link>
<h1 className="display-2">{BlogPost.title}</h1>
<h2 className="text-muted mt-3">Category: {capitalizeFirstLetter(BlogPost.category)}</h2>
<h4>{BlogPost.month} {BlogPost.day}</h4>
<p>Written by {BlogPost.author}</p>
<div className='mt-5 mb-5' dangerouslySetInnerHTML={createBlog()} />
<hr />
<p className="lead mb-5">
<Link to="http://localhost:8000/api/blog/" className="font-weight-bold">
Front Page
</Link>
</p>
</article>
))}
</div>
);
}
const capitalizeFirstLetter = (word) => {
if (word) return word.charAt(0).toUpperCase() + word.slice(1);
return "";
};
export default BlogDetail;
The code for App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import Layout from './hocs/Layout';
import Home from './components/Home';
import Blog from './components/Blog';
import BlogDetail from './components/BlogDetail';
import Category from './components/Category';
const App = () => (
<Router>
<Layout>
<Routes>
<Route path='/' element={<Home/>} />
<Route path='/blog' element={<Blog/>} />
<Route path='/blog/:id' element={<BlogDetail/>} />
<Route path='/category/:id' element={<Category/>} />
</Routes>
</Layout>
</Router>
);
export default App;
And the code for .env
REACT_APP_API_URL = 'http=//localhost:8000'
REACT_EDITOR=fathom
You need to put your arguments value in below method inside your useEffect method.
fetchBlogs(); // here pass your slug value
Error is mentioned that slug is undefined.
Use this method and it will solve your problem. Just pass your slug value.
const [blogs, setBlogs] = useState([]);
useEffect(() => {
const fetchBlogs = async(slug) => {
try {
const res = await axios.get(`http://localhost:8000/api/blog/${slug}`);
setBlogs(res.data);
} catch (err) {}
};
const slugData = window.location.href.split("/");
fetchBlogs(slugData[slugData.length-1]);// add your slug value in this method as an argument
}, []);
Another problem in this method :-
const createBlog = () => {
return {__html: blogs.content} //__html is also undefined. use like this "__html" instead of __html
};
replace this line
{blogs.map((BlogPost, slug) => (
with this
{(blogs || []).map((BlogPost, slug) => (
When calling the fetchBlog function in the useEffect, you didn't pass in the slug parameter

Why does optional chaining allows rendering when fetching data through useEffect in an app that uses context?

I'm new to the webdev world and want to learn ReactJS. I followed a tutorial I found on YouTube made by Traversy where he makes a task tracker and now I want to make some changes to it to learn and practice some more.
I want to use context for the appointments (originally named tasks in the tutorial), add a calendar with react-calendar and use react-router-dom.
I got stuck for a while trying to make the list render, because it only rendered "empty". Later on found this post with a similar issue to mine: Only run a useEffect fetch after first useEffect fetch has fired and setUser in context
I changed bits of my code based on that post and now it does render the appointment list, but I don't know why it didn't work before and I'm unsure on why it does work now. I don't even know if I'm using context correctly or just prop-drilling. Help would be greatly appreciated. Thank you.
Also, sorry if my code is a mess, I'm new at this.
App.js
import { createContext, useState, useEffect } from "react";
import Dashboard from "./views/Dashboard";
import './App.css';
import { BrowserRouter as Router, Route, Routes} from "react-router-dom";
import AddAppointmentForm from "./views/AddAppointmentForm";
export const AppContext = createContext();
export const AppUpdateContext = createContext();
function App() {
const [appointments, setAppointments] = useState([])
const updateAppointments = (apptList) => {
setAppointments(apptList)
}
return (
<AppContext.Provider value={ appointments }>
<AppUpdateContext.Provider value={ updateAppointments }>
<Router>
<Routes>
<Route path="/" element={<Dashboard appointments={appointments} />} />
{/* <Route path="/add" element={<AddAppointmentForm />} /> TBA */}
</Routes>
</Router>
</AppUpdateContext.Provider>
</AppContext.Provider>
);
}
export default App;
Dashboard.js
import { useEffect, useContext} from "react";
import { AppContext } from "../App";
import { AppUpdateContext } from "../App";
import AppointmentList from "../components/AppointmentList";
import Header from "../components/Header";
// function Dashboard() { // this is how it used to be
function Dashboard(props) {
const appointments = useContext(AppContext)
const setAppointments = useContext(AppUpdateContext)
const fetchAppointmentList = async () => {
const res = await fetch("http://localhost:5000/appointments");
const data = await res.json();
return data;
}
useEffect(() => {
const getAppointments = async () => {
const appointmentsFromServer = await fetchAppointmentList();
setAppointments(appointmentsFromServer);
}
getAppointments();
console.log("ñññññ",appointments)
}, []);
console.log("aagh",appointments)
return (
<div style={dashboardStyle}>
<Header />
{/* {appointments.lenght>0 ? (<AppointmentList />) : <p>empty</p>} this is how it used to be */}
<AppointmentList appointments={props?.appointments}/>
</div>
);
}
const dashboardStyle = {
maxWidth: "31.25rem",
overflow: "auto",
minHeight: "18.75rem",
border: "1px solid steelblue",
margin: "1.875rem auto",
padding: ".5rem",
boxSizing: "border-box",
}
export default Dashboard;
AppointmentList.js
import Appointment from "./Appointment";
import { AppContext } from "../App";
import { useContext } from "react";
function AppointmentList({ appointments }) {
// function AppointmentList() { // this is how it used to be
// const { appointments, setAppointments } = useContext(AppContext)
console.log("appList",appointments) // this is how it used to be
return (
<>
{
appointments.map(appt => (
<Appointment key={appt.id} appointment={appt} />
))
}
</>
);
}
export default AppointmentList;
Why does optional chaining allows rendering when fetching data through
useEffect in an app that uses context?
<AppointmentList appointments={props?.appointments}/>
It allows rendering by preventing accidental accesses into potentially null or undefined objects. The only way props could be undefined though is if you just simply don't declare it, i.e. const Dashboard = () => {.... vs const Dashboard = (props) => {.....
You are drilling the appointments state through props. AppointmentList can use the AppContext context to access the appointments state, while Dashboard can use the AppUpdateContext context to update the appointments state.
App
function App() {
const [appointments, setAppointments] = useState([]);
const updateAppointments = (apptList) => {
setAppointments(apptList);
};
return (
<AppContext.Provider value={{ appointments }}> // <-- need object here
<AppUpdateContext.Provider value={{ updateAppointments }}> // <-- and here
<Router>
<Routes>
<Route path="/" element={<Dashboard />} /> // <-- don't pass props
</Routes>
</Router>
</AppUpdateContext.Provider>
</AppContext.Provider>
);
}
Dashboard
function Dashboard() { // <-- no props
const { updateAppointments } = useContext(AppUpdateContext); // <-- access from context
const fetchAppointmentList = async () => {
const res = await fetch("http://localhost:5000/appointments");
const data = await res.json();
return data;
};
useEffect(() => {
const getAppointments = async () => {
const appointmentsFromServer = await fetchAppointmentList();
updateAppointments(appointmentsFromServer);
}
getAppointments();
}, []);
return (
<div style={dashboardStyle}>
<Header />
<AppointmentList /> // <-- don't pass props
</div>
);
}
AppointmentList
function AppointmentList() { // <-- no props
const { appointments } = useContext(AppContext); // <-- access from context
return appointments.map(appt => (
<Appointment key={appt.id} appointment={appt} />
));
}

I try to lay out a match for receiving a name from url but I receive a match undefinde

I encountered such an error match undefind. I'm taking an old course on React did everything as shown in the lesson but I get an error why. i don't understand why match undefinde. Maybe you need to pick up the match in another way or somehow pass it ??
import React from "react";
import { BrowserRouter, Route, Routes } from 'react-router-dom'
import { NavBar } from "./components/NavBar";
import { Home } from './pages/Home'
import { About } from './pages/About'
import { Profile } from './pages/Profile'
import { Alert } from "./components/Alert";
import { AlertState } from "./context/alert/AlertState";
import { GithubState } from "./context/github/GithunState";
function App() {
return (
<GithubState>
<AlertState>
<BrowserRouter>
<NavBar />
<div className="container pt-4">
<Alert alert={{text: 'Test Alert'}} />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/profile/:name" element={<Profile />} />
</Routes>
</div>
</BrowserRouter>
</AlertState>
</GithubState>
);
}
export default App;
import React from 'react'
import { useContext, useEffect } from 'react';
import { GithubContext } from '../context/github/githubContex';
export const Profile = ({match}) => {
// const github = useContext(GithubContext)
// const name = match.params.name
// useEffect(() => {
// github.getUser()
// github.getRepos(name)
// }, [])
console.log('asd',match);
return(
<div>
<h1>Profile page</h1>
</div>
)
}
import React, {useReducer} from "react"
import axios from 'axios'
import { CLEAR_USERS, GET_REPOS, GET_USER, SEARCH_USERS, SET_LOADING } from "../types"
import { GithubContext } from "./githubContex"
import { githubReducer } from "./githubReducer"
const CLIENT_ID = process.env.REACT_APP_CLIENT_ID
const CLIENT_SECRET = process.env.REACT_APP_CLIENT_SECRET
const withCreads = url => {
return `${url}client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}`
}
export const GithubState = ({children}) => {
const initialState = {
user: {},
users: [],
loading: false,
repos: []
}
const [state, dispatch] = useReducer(githubReducer, initialState)
const search = async value => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/search/users?q=${value}&`)
)
dispatch({
type: SEARCH_USERS,
payload: response.data.items
})
}
const getUser = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}?`)
)
dispatch({
type: GET_USER,
payload: response.data
})
}
const getRepos = async name => {
setLoading()
const response = await axios.get(
withCreads(`https://api.github.com/users/users/${name}/repos?per_page=5&`)
)
dispatch({
type: GET_REPOS,
payload: response.data
})
}
const clearUsers = () => dispatch({type: CLEAR_USERS})
const setLoading = () => dispatch({type: SET_LOADING})
const {user, users, repos, loading} = state
return (
<GithubContext.Provider value={{
setLoading, search, getUser, getRepos, clearUsers,
user, users, repos, loading
}}>
{children}
</GithubContext.Provider>
)
}
link to Github https://github.com/Eater228/React-Hooks
Check your package.json file and if you are using an older version of react-router-dom please use the latest version.
match prop should be passed down from the Route component and it will reflect the correct data as you are using react-router-dom.
Update
You are using element prop for rendering component and that's not the correct one. You should replace that element with component and it will work.
Update
Please consider using useParams hook instead of that match prop.
https://reactrouter.com/docs/en/v6/getting-started/overview#reading-url-parameters

Update Context state before component loads

Here I am using react-router and using conditionals for multiple routes. I am using context to store my state. I want to load my data from localStorage to the state before my App.js is loaded as based on that it tells on which routes should I navigate to.
userContext.js
import React, { useState, createContext, useEffect } from "react";
export const userContext = createContext();
export const UserProvider = (props) => {
const [user, setUser] = useState(null);
const loadFromLocalStorage = () => {
try {
const serializedState = localStorage.getItem("state");
if (serializedState == null) return undefined;
return JSON.parse(serializedState);
} catch (err) {
console.log(err);
return null;
}
};
useEffect(() => {
const state = loadFromLocalStorage();
console.log("state: ", state);
setUser(state);
}, []);
return (
<userContext.Provider value={[user, setUser]}>
{props.children}
</userContext.Provider>
);
};
export default userContext;
App.js
import React, { useContext, useEffect } from "react";
import { Switch, Route, Redirect } from "react-router";
import userContext from "./context/userContext";
const App = () => {
var routes = null;
const [user, setUser] = useContext(userContext);
console.log("app: ", user);
console.log(routes);
if (user == null) {
routes = (
<div>
<Switch>
<Route path="/login" component={SignUp} />
<Redirect to="/login"></Redirect>
</Switch>
</div>
);
} else {
routes = (
<div>
<Switch>
<Route exact path="/news" component={NewsList} />
<Redirect to="/news"></Redirect>
</Switch>
</div>
);
}
return <div className="App">{routes}</div>;
};
export default App;
Inside your UserProvider, don't render the children until you've loaded data from localstorage. This will require some extra state, but nothing too bad, and this is a common pattern for asynchronous context/state:
export const UserProvider = (props) => {
const [isReady, setIsReady] = useState(false);
// other code removed for brevity
useEffect(() => {
const state = loadFromLocalStorage();
setIsReady(true);
}, [])
return (
<userContext.Provider value={[user, setUser]}>
{isReady ? props.children : null}
</userContext.Provider>
);
}

Categories

Resources