React, UseContext changes not reflecting immediately - javascript

When I log out from the Home page, the state gets updated and the page shows a login option, but after logging in again, the page again shows a login option untill refreshed, after refreshing the page, it displays the username? Why does the context value does not reflect immediately?
Login.js:
const submit = () => {
Axios.post("http://localhost:3001/login", data).then((res) => {
localStorage.setItem("bankDetails", res.data[0].acc_no);
navigate("/Home");
});
};
return (
<form onSubmit={submit}>
<input ... />
...
</form>
)
Home.js:
import { useContext } from "react";
import { LoginContext, DetailsContext } from "../App";
function Account() {
const isValid = useContext(LoginContext);
const userDetails = useContext(DetailsContext);
const load = () => {
localStorage.removeItem("bankDetails");
window.location.reload();
}
return isValid ? (
<div className="personal-info">
<h3>Welcome {userDetails.Username}</h3>
<h3 onClick={load}>LogOut</h3>
</div>
) : (
<h1>You Need to Login First !!!</h1>
);
}
App.js:
export const LoginContext = React.createContext();
export const DetailsContext = React.createContext();
function App() {
const username = localStorage.getItem("bankDetails");
const [userDetails, setUserDetails] = useState();
const [isValid, setisValid] = useState(false);
useEffect(() => {
if (username !== null) {
Axios.post("http://localhost:3001/userDetails", {
username: username,
}).then((res) => {
if (res.data.err) {
console.log("err");
} else {
setUserDetails(res.data.details[0]);
setisValid(true);
}
});
}
}, [username]);
return (
<LoginContext.Provider value={isValid}>
<DetailsContext.Provider value={userDetails}>
<Router>
<Routes>
<Route path="/Login" element={<Login />} />
<Route path="/Home" element={<Home />} />
</Routes>
</Router>
</DetailsContext.Provider>
</LoginContext.Provider>
);
}

That's because inside submit function you didn't ask the context to update anything. Your App component is not watching changes inside the localStorage, the only thing you are updating after login. App as you did checks the localStorage only on load. That's why it works when you log in and then refresh the page.
What you can do that wouldn't change that much your current structure is to change App component as follow (I added comments where I changed things):
export const LoginContext = React.createContext();
export const DetailsContext = React.createContext();
function App() {
const [userDetails, setUserDetails] = useState();
const [isValid, setisValid] = useState(false);
useEffect(() => {
// I moved this line here in the callback
const username = localStorage.getItem("bankDetails");
if (username !== null) {
Axios.post("http://localhost:3001/userDetails", {
username: username,
}).then((res) => {
if (res.data.err) {
console.log("err");
} else {
setUserDetails(res.data.details[0]);
}
});
}
}, [isValid]); // I changed the dependencies array
return (
// I'am passing down setisValid as part of LoginContext
<LoginContext.Provider value={{ isValid, setisValid }}>
<DetailsContext.Provider value={userDetails}>
<Router>
<Routes>
<Route path="/Login" element={<Login />} />
<Route path="/Home" element={<Home />} />
</Routes>
</Router>
</DetailsContext.Provider>
</LoginContext.Provider>
);
}
Then grab both of isValid and setisValid inside Login component. Change your submit function and add an useEffect that would do the redirection. Like so:
const {isValid, setisValid} = useContext(LoginContext);
const submit = () => {
Axios.post("http://localhost:3001/login", data).then((res) => {
localStorage.setItem("bankDetails", res.data[0].acc_no);
setisValid(true);
};
useEffect(()=>{
if(isValid){
navigate("/home")
}
},[isValid])

Related

search parameters in React Router 6

I am having issues with react-router#6 when implementing search parameters.
To begin with, the app can search using the search form (for example, if the user searches dark, the app would direct to localhost:3000/search?query=dark to display the results),
can also use the URL in the search bar to be directed to the right page and results (for example, if the user use the URL localhost:3000/search?query=dark, it will direct to the page and display the results). Now, the issue is when the user types in the search form, it changes the URL by adding search parameters instantly. I am aware that this is caused by the setSearchParams() in the handleChange function, but is there any way around this to NOT change the URL when typing in the search form?
import React from 'react'
import Navbar from './Navbar'
import create from 'zustand'
import { useState, useEffect } from 'react'
import { useLocation, useNavigate, useSearchParams } from 'react-router-dom'
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTriangleExclamation } from '#fortawesome/free-solid-svg-icons'
// Zustand
let store = (set) => ({
// input: '',
// setInput: (value) => set({ input: value }),
allImages: [],
setAllImages: (images) => set({ allImages: images}),
totalResults: null,
setTotalResults: (num) => set({ totalResults: num}),
})
export const useMain = create(store)
function Header() {
const [error, setError] = useState(null)
const [showError, setShowError] = useState(false)
const [fadeOut, setFadeOut] = useState(false)
const [page, setPage] = useState(1)
const [searchParams, setSearchParams] = useSearchParams()
const query = searchParams.get('query') || ''
const allImages = useMain(state => state.allImages)
const setAllImages = useMain(state => state.setAllImages)
// const totalResults = useMain(state => state.totalResults)
const setTotalResults = useMain(state => state.setTotalResults)
function handleChange(event) {
// setInput(event.target.value)
setSearchParams({query: event.target.value})
}
async function fetchImages() {
try {
const res = await fetch(`https://api.unsplash.com/search/photos?&page=${page}&per_page=30&query=${query}&client_id=${process.env.REACT_APP_UNSPLASH_API_KEY}`)
const data = await res.json()
if (data.total !== 0) {
setAllImages(data.results)
setTotalResults(data.total)
}
} catch(error) {
setError(error)
}
}
let navigate = useNavigate()
const handleSubmit = async (event) => {
event.preventDefault()
fetchImages()
navigate(`/search?query=${query}`)
}
const location = useLocation()
useEffect(() => {
if (location.pathname === '/search' && allImages.length === 0) {
fetchImages()
navigate(`/search?query=${query}`)
}
}, [query])
// error
useEffect(() => {
if (error) {
setShowError(true)
setTimeout(() => {
setFadeOut(true)
setTimeout(() => {
setShowError(false)
}, 1000)
}, 5000)
}
}, [error])
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
value={query}
/>
</form>
</div>
{showError && <div className={`network-error ${fadeOut ? 'fade-out' : ''}`}>
<i><FontAwesomeIcon icon={faTriangleExclamation} /></i>
<div className='network-error--message'>
<h5>Network Error</h5>
<p>Please check your Internet connection and try again</p>
</div>
</div>}
</div>
)
}
export default Header
import './App.css';
import Main from './components/Main';
import Search from './components/pages/Search'
import Favorites from './components/pages/Favorites';
import Error from './components/pages/Error';
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import { SkeletonTheme } from 'react-loading-skeleton';
import { useDarkMode } from './components/Navbar';
function App() {
const darkMode = useDarkMode(state => state.darkMode)
let style
if (darkMode === 'light') {
style = 'wrapper'
} else {
style = 'wrapper-dark'
}
return (
<div className={style}>
<SkeletonTheme baseColor="#808080" highlightColor="#b1b1b1">
<BrowserRouter>
<Routes>
<Route path='/' element={<Main />} />
<Route path='search' element={<Search />} />
<Route path='favorites' element={<Favorites />} />
<Route path='*' element={<Error />} />
</Routes>
</BrowserRouter>
</SkeletonTheme>
</div>
);
}
export default App;
If I'm understanding your question correctly, you don't want to update the query queryString parameter in real-time as the form field is being updated, but sometime later, like when the form is submitted. Keep in mind that the setSeaarchParams function works just like the navigate function, but operates on the queryString.
You can manually update the searchParams object, and when the form is submitted, call setSearch params instead of navigate. Remove the value prop from the input element as we'll be updating the searchParams object.
function Header() {
...
const location = useLocation()
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams()
...
function handleChange(event) {
searchParams.set("query", event.target.value);
}
...
const handleSubmit = async (event) => {
event.preventDefault();
fetchImages();
setSearchParams(searchParams);
}
...
return (
<div className='header'>
<Navbar />
<h2 className='header--heading text-center text-light'>Find Images</h2>
<div className='header--form'>
<form onSubmit={handleSubmit}>
<input
className='header--form--input'
autoComplete='off'
type='text'
placeholder='Search'
onChange={handleChange}
name='input'
/>
</form>
</div>
...
</div>
)
}
export default Header

How can I get the updated data without refresh the page in React?

I have a component with create a blog. It works fine and navigates me to the home after adding the new blog object in the database, but I have to refresh the page to be able to see the updated data. So how can I see the updated blogs after navigation without refreshing the page?
import { useState } from "react";
import { useNavigate } from "react-router-dom";
export const Create = () => {
const [title, setTitle] = useState("");
const [body, setbody] = useState("");
const [author, setAuthor] = useState("mario");
const [isPending, setPending] = useState(false);
const nav = useNavigate();
const newBlog = { title, body, author };
const handelSubmit = (e) => {
e.preventDefault();
console.log(title);
console.log(body);
console.log(author);
setPending(true);
fetch("http://localhost:3001/blogs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newBlog),
}).then(() => {
console.log("newBlog Added");
setPending(false);
nav("/");
});
console.log(newBlog);
};
return (
<div className="create">
<h2>Add a New Blog</h2>
<form onSubmit={handelSubmit}>
<label htmlFor="">Blog title:</label>
<input
type="text"
required
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<label htmlFor="">Blog Body:</label>
<textarea
required
value={body}
onChange={(e) => setbody(e.target.value)}
></textarea>
<label htmlFor="">Blog author:</label>
<select value={author} onChange={(e) => setAuthor(e.target.value)}>
<option value="mario">Mario</option>
<option value="magdy">Magdy</option>
</select>
{!isPending && <button>Add Blog</button>}
{isPending && <button disabled>Adding Blog</button>}
</form>
</div>
);
};
//Home component implementation
import React from "react";
import Blog from "./../blog/Blog";
export const Home = () => {
// const { data, flag, error } = useFetch("http://localhost:3001/blogs");
const [blogs, setBlogs] = useState(null);
const [flag, setFlag] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetch("http://localhost:3001/blogs")
.then((res) => {
if (!res.ok) {
throw Error("Error in the API");
}
return res.json();
})
.then((data) => {
console.log(data);
setBlogs(data);
setFlag(!flag);
})
.catch((e) => {
console.log(e.message);
setError(e.message);
setFlag(!flag);
});
return () => {};
}, []);
return (
<div className="home">
{blogs && <Blog blogs={blogs} title="All Blogs" />}
{flag && <p>Loading...</p>}
{error !== null && <p>{error}</p>}
</div>
);
};
//Blog component implementation
import React from "react";
import { Link, Route, Routes } from "react-router-dom";
import { BlogDetails } from "../BlogDetails/BlogDetails";
import { Create } from "../create/Create";
import { Error } from "../Notfound/Error";
function Blog({ blogs, title }) {
return (
<Routes>
<Route
path="/"
element={
<div>
<h2>{title}</h2>
{blogs.map((blog) => {
return (
<div className="blog" key={blog.id}>
<Link to={`blogs/${blog.id}`}>
<h3>{blog.title}</h3>
<p>{blog.body}</p>
<h6>Written By {blog.author}</h6>
</Link>
</div>
);
})}
</div>
}
></Route>
<Route path="/create" element={<Create />}></Route>
<Route path="/blogs/:id" element={<BlogDetails />}></Route>
<Route path="*" element={<Error />}></Route>
</Routes>
);
}
export default Blog;
In order to update the blogs state, which is declared at the main point of the application, which is, for example, App.jsx, you must pass the state blogs and the set-state-action setBlogs as properties to the child component. For example:
The state in App.jsx:
const [blogs, setBlogs] = useState(null)
The child component (on which you will update the blogs state):
<Create blogs={blogs} setBlogs={setBlogs} />
On the fetch's then
fetch("http://localhost:3001/blogs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newBlog),
}).then((data) => {
console.log("newBlog Added");
setPending(false);
setBlogs([...blogs, newBlog])
nav("/");
});
console.log(newBlog);

React context provider updates state after context consumer renders

I am trying to implement Protected Routes in my app. I am using cookie-based session authentication.
The issue is: Whenever I try to access a protected page for the first time, the RequireAuth component has the isAuthenticated value as false and hence it navigates to /.
From the console logs, I can see Inside require auth. before Inside provide auth..
Questions:
Is using useEffect in the context provider the right way to set the auth state?
How do I make sure that the context provider state is set before accessing the context in the consumer RequireAuth?
I have a context provider ProvideAuth which makes an API call to check if the user is already authenticated.
const authContext = createContext();
export function ProvideAuth({ children }) {
const navigate = useNavigate();
const location = useLocation();
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [userInfo, setUserInfo] = useState({});
const fetchData = async () => {
const isAuthenticated = await CheckAuthentication();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const userInfo = await GetUserInfo();
setUserInfo(userInfo);
}
}
useEffect(() => {
console.log("Inside provide auth. " + isAuthenticated + " " + location.pathname);
fetchData();
}, []);
const value = {
isAuthenticated,
userInfo
};
return <authContext.Provider value={value}>{children}</authContext.Provider>;
}
Auth context consumer
export const useAuth = () => {
return useContext(authContext);
};
I use the context in a RequireAuth component to check if the user is already authenticated and redirect if not.
export default function RequireAuth({ children }) {
const { isAuthenticated, userInfo } = useAuth();
const location = useLocation();
useEffect(() => {
console.log("Inside require auth. " + isAuthenticated + " " + location.pathname);
}, []);
return isAuthenticated === true ?
(children ? children : <Outlet />) :
<Navigate to="/" replace state={{ from: location }} />;
}
The context provider is used in the App.js
return (
<ProvideAuth>
<div className='App'>
<Routes>
<Route exact path="/" element={<Home />} />
<Route path="/pricing" element={<Pricing />} />
<Route element={<RequireAuth /> }>
<Route path="/jobs" element={<Jobs />} >
<Route index element={<MyJobs />} />
<Route path="new" element={<NewJob />} />
<Route path=":jobId" element={<JobDetails />} />
<Route path=":jobId/stats" element={<JobStats />} />
</Route>
</Route>
<Route path="*" element={<NotFound />} />
</Routes>
</div>
</ProvideAuth>
);
That's because that useEffect in ProvideAuth is as any useEffect an asynchronous task, which means the component and its children may render before its callback gets executed.
A solution is to set up a loading state in ProvideAuth, called for example isCheckingAuth, set to true by default, and to false after you have done all the fetching. And you pass it down to RequireAuth, like so :
const authContext = createContext();
export function ProvideAuth({ children }) {
const navigate = useNavigate();
const location = useLocation();
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [userInfo, setUserInfo] = useState({});
const fetchData = async () => {
const isAuthenticated = await CheckAuthentication();
setIsAuthenticated(isAuthenticated);
if (isAuthenticated) {
const userInfo = await GetUserInfo();
setUserInfo(userInfo);
}
setIsCheckingAuth(false)
}
useEffect(() => {
console.log("Inside provide auth. " + isAuthenticated + " " + location.pathname);
fetchData();
}, []);
const value = {
isAuthenticated,
userInfo,
isCheckingAuth
};
return <authContext.Provider value={value}>{children}</authContext.Provider>;
}
You use that isCheckingAuth inRequireAuth to show a loader while the fetching is being done, this way:
export default function RequireAuth({ children }) {
const { isAuthenticated, userInfo, isCheckingAuth } = useAuth();
const location = useLocation();
useEffect(() => {
if(isCheckingAuth) return;
console.log("Inside require auth. " + isAuthenticated + " " + location.pathname);
}, [isCheckingAuth]);
if(isCheckingAuth) return <div>Loading...</div>
return isAuthenticated === true ?
(children ? children : <Outlet />) :
<Navigate to="/" replace state={{ from: location }} />;
}
What you can do is check, If the request is processed or not. If processing show loader if any error shows some error msg or redirect. If everything is fine load provider.
const authContext = createContext();
export function ProvideAuth({ children }) {
const [state, setState] = useState({
user: null,
isAuthenticated: false,
isLoading: false,
error: null,
});
useEffect(() => {
const fetchData = async () => {
try {
const isAuthenticated = await CheckAuthentication();
if (isAuthenticated) {
const user = await GetUserInfo();
setState((prev) => ({ ...prev, isAuthenticated, user }));
}
} catch (error) {
setState((prev) => ({ ...prev, error }));
} finally {
setState((prev) => ({ ...prev, isLoading: false }));
}
};
fetchData();
}, []);
if (state.isLoading) return <Loading />;
if (state.error) return <ErrorMessage error={state.error} />;
return <authContext.Provider value={state}>{children}</authContext.Provider>;
}

React - How to stay on the same page even if it was refreshed?

I'm using react-router for the link to the different pages. Everything works fine, however, once I'll refresh the page, it'll go to the login page for a moment and it'll go back to the homepage. It was even worse if I'll go to the admin page, refreshing the page will direct the user to the login page, however, the user is still logged in and only displays the login page. I'm also using Firebase Firestore and firebase authentication.
app.js
const App = (props) => {
const { setCurrentUser, currentUser } = props;
const admin = checkUserAdmin(currentUser);
console.log(admin);
useEffect(() => {
const authListener = auth.onAuthStateChanged(async (userAuth) => {
if (userAuth) {
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot((snapshot) => {
setCurrentUser({
id: snapshot.id,
...snapshot.data(),
});
});
}
setCurrentUser(userAuth);
});
return () => {
authListener();
};
}, []);
return (
<div className="App">
<Switch>
<Route
exact
path="/login"
render={() => (
<MainLayout>
<LoginPage />
</MainLayout>
)}
/>
<Route
exact
path="/profile"
render={() => (
<WithAuth>
<MainLayout>
<ProfilePage />
</MainLayout>
</WithAuth>
)}
/>
<Route
exact
path="/admin"
render={() => (
<WithAdmin>
<AdminHome />
</WithAdmin>
)}
/>
</Switch>
</div>
);
};
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser,
});
const mapDispatchToProps = (dispatch) => ({
setCurrentUser: (user) => dispatch(setCurrentUser(user)),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
withAuth - restricting the users for the pages. If currentUser is a guest user, it directs the user to the login page.
import { useAuth } from "./../custom-hooks";
import { withRouter } from "react-router-dom";
const WithAuth = (props) => useAuth(props) && props.children;
export default withRouter(WithAuth);
useAuth - restricting the users for the pages. If currentUser is a guest user, it directs the user to the login page.
const mapState = ({ user }) => ({
currentUser: user.currentUser,
});
const useAuth = (props) => {
const { currentUser } = useSelector(mapState);
useEffect(() => {
if (!currentUser) {
props.history.push("/login");
}
}, [currentUser]);
return currentUser;
};
export default useAuth;
withAdmin - pages only accessible to the admin
import { useAdmin } from "../../custom-hooks";
const WithAdmin = (props) => useAdmin(props) && props.children;
export default WithAdmin;
useAdmin - pages only accessible to the admin. If user is not an admin, it directs the user to the login page.
const mapState = ({ user }) => ({
currentUser: user.currentUser,
});
const useAdmin = (props) => {
const { currentUser } = useSelector(mapState);
const history = useHistory();
useEffect(() => {
if (!checkUserAdmin(currentUser)) {
history.push("/login");
}
}, [currentUser]);
return currentUser;
};
export default useAdmin;
Below is my index.js
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Reducers:
userTypes:
const userTypes = {
SET_CURRENT_USER: "SET_CURRENT_USER",
};
export default userTypes;
userActions:
import userTypes from "./user.types";
export const setCurrentUser = (user) => ({
type: userTypes.SET_CURRENT_USER,
payload: user,
});
userReducer:
import userTypes from "./user.types";
const INITIAL_STATE = {
currentUser: null,
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case userTypes.SET_CURRENT_USER:
return {
...state,
currentUser: action.payload,
};
default:
return state;
}
};
export default userReducer;
rootReducer:
import { combineReducers } from "redux";
import userReducer from "./user/user.reducer";
export default combineReducers({
user: userReducer,
});
store.js
import { createStore, applyMiddleware } from "redux";
import logger from "redux-logger";
import rootReducer from "./rootReducer";
export const middlewares = [logger];
export const store = createStore(rootReducer, applyMiddleware(...middlewares));
export default store;
checkUserAdmin.js
export const checkUserAdmin = (currentUser) => {
if (!currentUser || !Array.isArray(currentUser.roles)) return false;
const { roles } = currentUser;
if (roles.includes("admin")) return true;
return false;
};
From the App.js, I console.log(currentUser) and this is what is shows:
I suggest adding an authPending state to your userReducer, initially true, and also set/cleared when the firestore logic is handing user changes.
userReducer & actions
const userTypes = {
SET_AUTH_PENDING: "SET_AUTH_PENDING",
SET_CURRENT_USER: "SET_CURRENT_USER",
};
const setAuthPending = pending => ({
type: userTypes.SET_AUTH_PENDING,
payload: pending,
});
const INITIAL_STATE = {
authPending: true,
currentUser: null,
};
const userReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case userTypes.SET_CURRENT_USER:
return {
...state,
authPending: false
currentUser: action.payload,
};
case userTypes.SET_AUTH_PENDING:
return {
...state,
authPending: action.payload,
};
default:
return state;
}
};
app.js
const App = (props) => {
const {
setAuthPending, // <-- access action
setCurrentUser,
currentUser
} = props;
const admin = checkUserAdmin(currentUser);
console.log(admin);
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(async (userAuth) => {
setAuthPending(true); // <-- start auth pending
if (userAuth) {
const userRef = await handleUserProfile(userAuth);
userRef.onSnapshot((snapshot) => {
setCurrentUser({ // <-- will clear auth pending
id: snapshot.id,
...snapshot.data(),
});
});
} else {
setCurrentUser(null); // <-- clear user data and pending
}
});
return () => {
unsubscribe();
};
}, []);
return (
<div className="App">
<Switch>
...
</Switch>
</div>
);
};
const mapStateToProps = ({ user }) => ({
currentUser: user.currentUser,
});
const mapDispatchToProps = {
setAuthPending, // <-- wrap action creator in call to dispatch
setCurrentUser,
};
Hooks & Wrappers
For these I suggest abstracting the logic into custom Route components.
const AuthRoute = props => {
const { authPending, currentUser } = useSelector(state => state.user);
if (authPending) {
return "Loading..."; // or maybe a loading spinner
};
return currentUser ? (
<Route {...props} />
) : (
<Redirect to="/login" />
);
};
const AdminRoute = props => {
const { authPending, currentUser } = useSelector(state => state.user);
if (authPending) {
return "Loading..."; // or maybe a loading spinner
};
return checkUserAdmin(currentUser) ? (
<Route {...props} />
) : (
<Redirect to="/login" />
);
};
Then the routes become
<Switch>
<Route
exact
path="/"
render={() => (
<MainLayout>
<Homepage />
</MainLayout>
)}
/>
<Route
exact
path="/login"
render={() => (
<MainLayout>
<LoginPage />
</MainLayout>
)}
/>
<AuthRoute
exact
path="/profile"
render={() => (
<MainLayout>
<ProfilePage />
</MainLayout>
)}
/>
<AdminRoute
exact
path="/admin"
component={AdminHome}
/>
</Switch>
After this, you may want to look into persisting your redux state into localStorage, and repopulating your redux state from localStorage when you are instantiating the store (the preloadedState parameter) object when your app is loading. You can manage yourself or look into something like redux-persist.
When a user login you can store some values about the user in localStorage,like username or a token or just a login ,
localStorage.setItem(IS_LOGIN, true);
After that you can use that in your userReducer, when you initiate state you can directly determine the user is login or not.
const INITIAL_STATE = {
isLogin: localStorage.IS_LOGIN
};
now you can determine a user is login or not before the page load. If you wanna push user to the login page you can use in useEffect
useEffect(() => {
if (!isLogin) {
props.history.push("/login");
}
}, [isLogin]);
return isLogin;
};
when your app first loaded there is no user information on the userReducer, because of that when page load you will be directing to the login page.

What is the best way to pass in props to a react router route?

I have a react component I need to render that takes one argument of a string when it is initialized. I need a button to click on that will redirect and render this new component with the string. It sends the string I want when it console.log(pro). everytinme I click on the button it goes to a blank screen and doesn't load.
My routes.js looks like
const Routes = (props) => {
return (
<Switch>
<Route exact path="/member" component={() => <Member user={props.state.member} />} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes
The original component looks like this
const Posts = (props) => {
const dispatch = useDispatch();
const [postCount, setPostCount] = useState(0);
const [member, setMember] = useState({});
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push('/member'
);
console.log('----------- member------------') // console.log(addr)
return (
<Member user={member}><Member/>
);
}
return (
<div>
{socialNetworkContract.posts.map((p, index) => {
return <tr key={index}>
<button onClick={() => getProfile(p.publisher)}>Profile</button>
</tr>})}
</div>
)
}
export default withRouter(Posts);
The component I'm trying to render from the Posts component needs to be rendered with a string
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = props.location;
const [profile, setProfile] = useState({});
useEffect(() => {
const doEffects = async () => {
try {
const pro = socialNetworkContract.members[0]
console.log(pro)
const p = await incidentsInstance.usersProfile(pro, { from: accounts[0] });
setProfile(p)
} catch (e) {
console.error(e)
}
}
doEffects();
}, [profile]);
return (
<div class="container">
{profile.name}
</div>
)
}
export default Member;
You can pass an extra data to a route using state attribute with history.push
const getProfile = async (member) => {
const addr = dispatch({ type: 'ADD_MEMBER', response: member })
console.log(member)
props.history.push({
path: '/member',
state: { member }
});
}
Once you do that you can access it in the rendered route from location.state
import {
useLocation
} from "react-router-dom";
const Member = (props)=> {
const [user, setUser] = useState({});
const { state } = useLocation();
console.log(state.member);
const [profile, setProfile] = useState({});
...
}
export default Member;
Also you do not need to pass on anything while rendering the Route
const Routes = (props) => {
return (
<Switch>
<Route exact path="/member" component={Member} />
<Route path="/posts" exact component={Posts} />
<Redirect exact to="/" />
</Switch>
)
}
export default Routes

Categories

Resources