I am trying to create a shared global state for all components that an app needs, and instead of relying on props drilling or redux, I am trying to achieve that with the React Context.
Why does my user context not survive when I switch between routes? The application bellow illustrates the issue.
Do I need to use any other hook in conjunction with useContext?
//index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { AuthenticationProvider } from "./AuthenticationProvider";
const Index = () => {
return (
<AuthenticationProvider>
<App />
</AuthenticationProvider>
);
}
ReactDOM.render(<Index />, document.getElementById('root'));
//App.js
import React, { useState, useContext } from 'react';
import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
import './App.css';
import { AuthenticationContext } from './AuthenticationProvider';
function AddUser() {
const [formUser, setFormUser] = useState("");
const [user, setUser] = useContext(AuthenticationContext);
const handleSubmit = async (event) => {
event.preventDefault();
setUser(formUser);
}
return (
<React.Fragment>
Form user: {formUser}.
<form id="form1" onSubmit={handleSubmit}>
<input type="text" id="user" onChange={event => setFormUser(event.target.value)} />
<input type="submit" value="Save" />
</form>
<br/>
Current user: {user}
<br/>
Back to home
</React.Fragment>
);
}
function Home() {
const [user, setUser] = useContext(AuthenticationContext);
return (
<React.Fragment>
<div className="App">
Hello {user}.
<br/>
Add user
</div>
</React.Fragment>
);
}
function App() {
return (
<Router>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/add" component={AddUser} />
</Switch>
</Router>
);
}
export default App;
//AuthenticationProvider.js
import React, { useState, createContext } from "react";
const DEFAULT_STATE = "";
export const AuthenticationContext = createContext(DEFAULT_STATE);
export const AuthenticationProvider = ({ children }) => {
const [user, setUser] = useState(DEFAULT_STATE);
return (
<AuthenticationContext.Provider value={[user, setUser]} >
{children}
</AuthenticationContext.Provider>
);
}
The problem is that you used a regular <a> link to navigate through the app and every time you go from Home to addUser the app refreshes. To navigate through the app without refreshing the page use the Link component from react-router-dom
in Home and AddUser change the a links to the Link component
import { Link } from "react-router-dom";
function Home() {
const { user, setUser } = useContext(AuthenticationContext);
return (
<React.Fragment>
<div className="App">
Hello {user}.
<br />
<Link to="/add">Add user</Link> <-- Changed a to Link
</div>
</React.Fragment>
);
}
function AddUser() {
const [formUser, setFormUser] = useState("");
const [user, setUser] = useContext(AuthenticationContext);
const handleSubmit = async (event) => {
event.preventDefault();
setUser(formUser);
}
return (
<React.Fragment>
Form user: {formUser}.
<form id="form1" onSubmit={handleSubmit}>
<input type="text" id="user" onChange={event => setFormUser(event.target.value)} />
<input type="submit" value="Save" />
</form>
<br />
Current user: {user}
<br />
<Link to="/">Back to home</Link> <-- Changed a to Link
</React.Fragment>
);
}
Related
My 4 js files are the following.i use react router v6 and after the signin in useEffect tried to redirect in chats page.
import "./App.css";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Login from "./components/Login";
import Chats from "./components/Chats";
import { AuthProvider } from "./contexts/AuthContext";
function App() {
return (
<div style={{ fontFamily: "Avenir" }}>
<Router>
<AuthProvider>
<Routes>
<Route path="/" element={<Login />} />
<Route path="/chats" element={<Chats />} />
</Routes>
</AuthProvider>
</Router>
</div>
);
}
export default App;
import React from "react";
import { useNavigate } from "react-router-dom";
import { Col, Row } from "react-grid-system";
import {
ChatEngineWrapper,
Socket,
ChatList,
ChatFeed,
ChatSettings,
} from "react-chat-engine";
import { auth } from "../firebase";
import { useAuth } from "../contexts/AuthContext";
const Chats = () => {
const navigate = useNavigate();
const { user } = useAuth();
console.log(user);
const handleLogout = async () => {
await auth.signOut();
navigate("/", { replace: true });
};
return (
<div className="chats-page">
<div className="nav-bar">
<div className="logo-tab">TotalChat</div>
<div onClick={handleLogout} className="logout-tab">
Logout
</div>
</div>
<ChatEngineWrapper height="calc(100vh - 66px)">
<Socket
projectID={process.env.REACT_APP_PROJECT_ID}
userName={process.env.REACT_APP_USERNAME}
userSecret={process.env.REACT_APP_USER_SECRET}
/>
<Row>
<Col xs={0} sm={3}>
<ChatList />
</Col>
<Col xs={12} sm={6}>
<ChatFeed />
</Col>
<Col xs={0} sm={3}>
<ChatSettings />
</Col>
</Row>
</ChatEngineWrapper>
</div>
);
};
export default Chats;
import React from "react";
import { GoogleOutlined, FacebookOutlined } from "#ant-design/icons";
import { auth } from "../firebase";
import firebase from "firebase/compat/app";
const Login = () => {
return (
<div id="login-page">
<div id="login-card">
<h2>Welcome To Total Chat!</h2>
<div
className="login-button google"
onClick={() =>
auth.signInWithRedirect(new firebase.auth.GoogleAuthProvider())
}
>
<GoogleOutlined /> Sign In with Google
</div>
<br />
<br />
<div
className="login-button facebook"
onClick={() =>
auth.signInWithRedirect(new firebase.auth.FacebookAuthProvider())
}
>
<FacebookOutlined /> Sign In with Facebook
</div>
</div>
</div>
);
};
export default Login;
import React, { useContext, useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { auth } from "../firebase";
// import io from "socket.io-client";
// let socket = io.connect("wss://localhost:3000");
const AuthContext = React.createContext();
export const useAuth = () => useContext(AuthContext);
export const AuthProvider = ({ children }) => {
const [loading, setLoading] = useState(true);
const [user, setUser] = useState("");
const navigate = useNavigate();
useEffect(() => {
auth.onAuthStateChanged((user) => {
// socket.on();
setUser(user);
setLoading(false);
if (user) navigate("/chats");
});
}, [user, navigate]);
const value = { user };
return (
<AuthContext.Provider value={value}>
{!loading && children}
</AuthContext.Provider>
);
};
Errors
closed socket
I am completely new to this and i do not know if the redirection problem also caused by socket or connection issues.If anybody has an idea about it i would be grateful.Thanks in advance!
I'm learning reactjs and I'm trying to simulate an "Authentication" method on the front-end with reactjs and json-server and I'm facing a problem.
I have theses components:
Apps.js (with all the Routes)
Login.jsx ( with a form and all the logic )
ProtectedRoutes.jsx (as a function component to do a simple verification if the user is logged or no, and protected routes.
Clients.jsx (with all the lists fetched from the json-server, working properly, not important here)
I would like to create one state (isLogged / setIsLogged) to be trigged as "true" when the user hit the submit on my "Login.jsx", reusing this component, since the current state is "false". But I'm not figuring out how to do it. I'm not understanding how I can access the functions / state to do this.
App.js
import "./App.css";
import Login from "./pages/Login";
import Register from "./pages/Register";
import Clients from "./pages/Clients";
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import ProtectedRoutes from "./components/ProtectedRoutes";
function App() {
return (
<>
<Router>
<Routes>
<Route path="/" element={<Login />} />
<Route element={<ProtectedRoutes isLogged={false} />}>
<Route path="/register" element={<Register />} />
<Route path="/clients" element={<Clients />} />
</Route>
</Routes>
</Router>
</>
);
}
export default App;
ProtectedRoutes.jsx
import { Navigate, Outlet } from "react-router-dom";
const ProtectedRoutes = ({ isLogged }) => {
return isLogged ? <Outlet /> : <Navigate to="/" />;
};
export default ProtectedRoutes;
Login.jsx
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { validateEmail, validatePassword } from "../utils/regex";
import logoImg from "../assets/logo-troupe.png";
import Navbar from "../components/Navbar";
const Login = () => {
const navigateTo = useNavigate();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailErr, setEmailErr] = useState(false);
const [passwordErr, setPasswordErr] = useState(false);
// input login validations
const validateEmailPassword = () => {
if (!validateEmail.test(email)) {
setEmailErr(true);
} else {
setEmailErr(false);
}
if (!validatePassword.test(password)) {
setPasswordErr(true);
} else {
setPasswordErr(false);
navigateTo("/clients");
}
};
//function to generate a random "token" simulating a login on backend
const handleStorageToken = () => {
const userToken = {
email,
password,
};
localStorage.setItem("Token", JSON.stringify(userToken));
};
const handleSubmit = (e) => {
e.preventDefault();
validateEmailPassword();
handleStorageToken();
};
return (
<div className="main-container">
<Navbar />
<div className="login-container">
<a href="#">
<img src={logoImg} alt="logo" tooltip="Troupe website" />
</a>
<h1>Login</h1>
<form>
<div className="form-group">
<label>E-mail</label>
<input
type="email"
placeholder="Enter your e-mail"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
{emailErr && (
<p className="validation-error">
Insira um e-mail válido!
</p>
)}
</div>
<div className="form-group">
<label>Password</label>
<input
type="password"
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
{passwordErr && (
<p className="validation-error">
Senha inválida! Mínimo 4 caracteres, 1 letra and 1
número
</p>
)}
<button onClick={handleSubmit} className="btn-login" type="submit">
Login
</button>
</div>
</form>
</div>
</div>
);
};
export default Login;
Fairly new to react and trying to get the router to work for me. I'm pulling in data from a third party api using a simple form. When the data is retrieved I'd like to display it in a new route, and then ultimately have another route for each item retrieved. When I submit the form I just get a '?' in the route params. If I enter the route manually then submit the form the data displays. How can I get the data to display on form submit?
import axios from "axios";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import "./App.css";
import RecipeList from "./RecipeList";
import Header from "./Header";
function App() {
const [recipes, setRecipes] = useState([]);
const [query, setQuery] = useState("");
const [search, setSearch] = useState("");
const APP_ID = "XXXXXXXX";
const APP_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const url = `https://api.edamam.com/api/recipes/v2?type=public&q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`;
const getRecipes = async () => {
const res = await axios(url);
const data = await res.data.hits;
console.log(data);
setRecipes(data);
};
useEffect(() => {
getRecipes();
}, [query]);
const updateSearch = (e) => {
setSearch(e.target.value);
console.log(search);
};
const getSearchQuery = () => {
e.preventDefault();
setQuery(search);
setSearch("");
};
return (
<div className="App">
<Header />
<div>
<div className="container">
<form className="search-form" onSubmit={getSearchQuery}>
<input
className="search-input"
type="text"
value={search}
onChange={updateSearch}
placeholder="search by food name"
/>
<button className="search-button" type="submit">
Search Recipes
</button>
</form>
</div>
</div>
{/* <RecipeList recipes={recipes}/> */}
<Router>
<Routes>
<Route path="/recipes" element={<RecipeList recipes={recipes}/>} />
<Route path="/recipes/:id" />
</Routes>
</Router>
</div>
);
}
export default App;
import React from "react";
import { Link } from "react-router-dom";
const RecipeList = ({ recipes }) => {
console.log(recipes);
return (
<div>
{recipes.map(({ recipe }, id) => (
<Link to={`recipes/${recipe.label}`}>
<p key={id}>{recipe.label}</p>
</Link>
))}
</div>
);
};
export default RecipeList;
If I understand your question/issue you are having issue linking to a specific recipe. I suspect it is because you are using a relative link, so you are linking to a "/recipes/recipes/<label".
Either use absolute link paths, i.e. using a leading "/":
<Link to={`/recipes/${recipe.label}`}>
<p key={id}>{recipe.label}</p>
</Link>
Or use a correct relative path, i.e. only append the next level path segment, in other words, appending recipe.label to "/recipes":
<Link to={`${recipe.label}`}>
<p key={id}>{recipe.label}</p>
</Link>
If you wanting to start on "/" and submit the form and navigate to "/recipes" then issue an imperative navigation after submitting the form. Import the useNavigate hook to issue the imperative navigation and move the Router to wrap the App component so the routing context is provided to it and the useNavigate hook can work properly.
import axios from "axios";
import { BrowserRouter as Router, Routes, Route, useNavigate } from "react-router-dom";
import "./App.css";
import RecipeList from "./RecipeList";
import Header from "./Header";
function App() {
const navigate = useNavigate(); // <-- use navigate hook
const [recipes, setRecipes] = useState([]);
const [query, setQuery] = useState("");
const [search, setSearch] = useState("");
const APP_ID = "XXXXXXXX";
const APP_KEY = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
const url = `https://api.edamam.com/api/recipes/v2?type=public&q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`;
const getRecipes = async () => {
const res = await axios(url);
const data = await res.data.hits;
console.log(data);
setRecipes(data);
};
useEffect(() => {
getRecipes();
}, [query]);
const updateSearch = (e) => {
setSearch(e.target.value);
console.log(search);
};
const getSearchQuery = () => {
e.preventDefault();
setQuery(search);
setSearch("");
navigate("/recipes"); // <-- imperative navigation
};
return (
<div className="App">
<Header />
<div>
<div className="container">
<form className="search-form" onSubmit={getSearchQuery}>
<input
className="search-input"
type="text"
value={search}
onChange={updateSearch}
placeholder="search by food name"
/>
<button className="search-button" type="submit">
Search Recipes
</button>
</form>
</div>
</div>
<Routes>
<Route path="/recipes" element={<RecipeList recipes={recipes}/>} />
<Route path="/recipes/:id" />
</Router>
</div>
);
}
index.js
<Router>
<App />
</Router>
Fairly new to react here. I'm making a small recipe finder app with an api. After getting the data, I'm mapping through the results and displaying them in a component. What I want to do is display the details of each recipe through another component in another route. I'm not sure how to do this. I thought I could pass the mapped recipe through Link, but it's not working. Here is what I have so far.
Index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById('root')
);
App.js
import React, { useState, useEffect} from "react";
import axios from "axios";
import { BrowserRouter as Router, Routes, Route, useNavigate} from "react-router-dom";
import "./App.css";
import RecipeList from "./RecipeList";
import Recipe from "./Recipe";
import Header from "./Header";
function App() {
const navigate = useNavigate();
const [recipes, setRecipes] = useState([]);
const [query, setQuery] = useState("");
const [search, setSearch] = useState("");
const APP_ID = "XXXXXXXXX";
const APP_KEY = "XXXXXXXXXXXXXXXXXXXXXX";
const url = `https://api.edamam.com/api/recipes/v2?type=public&q=${query}&app_id=${APP_ID}&app_key=${APP_KEY}`;
const getRecipes = async () => {
const res = await axios(url);
const data = await res.data.hits;
console.log(data);
setRecipes(data);
};
useEffect(() => {
getRecipes();
}, [query]);
const updateSearch = (e) => {
setSearch(e.target.value);
console.log(search);
};
const getSearchQuery = (e) => {
e.preventDefault();
setQuery(search);
setSearch("");
navigate("/recipes");
};
return (
<div className="App">
<Header />
<div>
<div className="container">
<form className="search-form" onSubmit={getSearchQuery}>
<input
className="search-input"
type="text"
value={search}
onChange={updateSearch}
placeholder="search by food name"
/>
</form>
</div>
</div>
<Routes>
<Route path="/recipes" element={<RecipeList recipes={recipes} />} />
<Route path="/recipes/:id" element={<Recipe recipes={recipes} />}/>
</Routes>
</div>
);
}
export default App;
RecipeList.jsx
import React from "react";
import { Link } from "react-router-dom";
const RecipeList = ({ recipes }) => {
return (
<div className="container">
<div className="grid-container">
{recipes.map(({ recipe }) => (
<Link to={`/recipes/${recipe.label}`}>
<img key={recipe.image} src={recipe.image} alt="" />
<p key={recipe.label}>{recipe.label}</p>
<p>{recipe.id}</p>
</Link>
))}
</div>
</div>
);
};
export default RecipeList;
Recipe.jsx
const Recipe = ({recipe}) => {
return (
<div>
<h1>{recipe.label}</h1>
</div>
)
}
export default Recipe
Am I even close???
You are passing the entire recipes array to both routed components.
<Routes>
<Route path="/recipes" element={<RecipeList recipes={recipes} />} />
<Route path="/recipes/:id" element={<Recipe recipes={recipes} />}/>
</Routes>
So Recipe can use the entire array and the id route match param to search the passed array and render the exact recipe by matching label.
import { useParams } from 'react-router-dom';
const Recipe = ({ recipes }) => {
const { id } = useParams();
const recipe = recipes.find(recipe => recipe.label === id); // *
return recipe ? (
<div>
<h1>{recipe.label}</h1>
</div>
) : null;
};
* Note: Since you call the route param id it may make more sense to us the recipe.id for the link.
{recipes.map(({ recipe }) => (
<Link to={`/recipes/${recipe.id}`}>
<img key={recipe.image} src={recipe.image} alt="" />
<p key={recipe.label}>{recipe.label}</p>
<p>{recipe.id}</p>
</Link>
))}
...
const recipe = recipes.find(recipe => recipe.id === id);
This is My App.js where all the Routes define Under Router. It's work fine when i jump from one Link to other in those component that are not using redux. but when i click on Redux connected component it's render component but then when i click on any other Link they just change Url Not view.
This is App js File:-
import React, { useEffect, Fragment } from "react";
import { Router, Route, Switch } from "react-router-dom";
import history from "./history";
import Navbar from "./components/layouts/Navbar";
import Landing from "./components/layouts/Landing";
import Profiles from "./components/profiles/Profiles";
import Login from "./components/auth/Login";
import Register from "./components/auth/Register";
import { loadUser } from "./actions/auth";
import { useDispatch } from "react-redux";
const App = () => {
const dispatch = useDispatch(() => loadUser());
useEffect(() => {
dispatch(loadUser());
}, [dispatch]);
return (
<Router history={history}>
<Navbar />
<Route exact path='/' component={Landing} />
<section className='container'>
<Alert />
<Switch>
<Route exact path='/register' component={Register} />
<Route exact path='/login' component={Login} />
<Route path='/profiles' component={Profiles} />
</Switch>
</section>
</Router>
);
};
export default App;
Both Register And LogIn Workimg well when navigating through each other but when I jump to component that using redux profiles, it loads and work but after that when i want to jump from profiles to Register login they just change url not view.
this is My profiles file that using redux and creating issue.
import React, { Fragment, useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { getProfiles } from "../../actions/profile";
import Spinner from "../layouts/Spinner";
import ProfileItems from "./ProfileItems";
import { withRouter } from "react-router-dom";
const Profiles = () => {
const profile = useSelector(state => state.profile);
const { profiles, loading } = profile;
const dispatch = useDispatch(() => getProfiles());
useEffect(() => dispatch(getProfiles()), [dispatch]);
return (
<Fragment>
{loading ? (
<Spinner />
) : (
<Fragment>
<h1 className='large text-primary'>Developers</h1>
<p className='lead'>
<i className='fab fa-connectdevelop'></i> Browse and Connect With
Developers...
</p>
<div className='profiles'>
{profiles.length > 0 ? (
profiles.map(profile => (
<ProfileItems key={profile._id} profile={profile} />
))
) : (
<h4>profile not Found !!!...</h4>
)}
</div>
</Fragment>
)}
</Fragment>
);
};
export default withRouter(Profiles);
And These are My Login And Register component that are working well when navigate to each other. when they go to profiles after that when i click on link of them they just change urls in address bar but not changing view. It's Login page Register is similar to this..
import React, { Fragment, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import { login } from "../../actions/auth";
import { Link, Redirect } from "react-router-dom";
const Login = () => {
const dispatch = useDispatch(() => login());
const isAuthenticated = useSelector(state
=>state.auth.isAuthenticated);
const [formData, setFormData] = useState({
email: "",
password: ""
});
const { email, password } = formData;
const onChange = e => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const onSubmit = e => {
e.preventDefault();
dispatch(login(email, password));
};
if (isAuthenticated) {
return <Redirect to='/dashboard' />;
}
return (
<Fragment>
<h1 className='large text-primary'>Sign In</h1>
<p className='lead'>
<i className='fas fa-user'>Sign In Your Account!!!</i>
</p>
<form onSubmit={e => onSubmit(e)} className='form'>
<div className='form-group'>
<input
type='email'
name='email'
placeholder='Enter Your Email'
value={email}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input
type='password'
name='password'
placeholder='Enter Your Password'
value={password}
onChange={e => onChange(e)}
/>
</div>
<div className='form-group'>
<input type='submit' value='LogIn' className='btn btn-primary' />
</div>
</form>
<p className='my-1'>
Don't have an Account <Link to='/register'>Sign Up</Link>
</p>
</Fragment>
);
};
export default Login;
I searched this alot and mostly got ans use withRouter I tried that one as u can see but still not working or maybe i am not using withRouter on correct component.
I'll do Provide any other information that you need to know about my code if you want and I am using react-redux hooks instead of using connect
I had faced the same problem in the past.
At that time I solve this issue with connected-react-router.
this is how to use the connected-react-router.
how to use connected-react-router