Pass res.data from one post request to different React component - node.js

I have created a login form in React that uses an Axios post request like this:
axios.post('http://localhost:8080/users/validate', {
email: email,
password: password,
})
.then((res) => {
setError(res.data);
//If validation passed
if (res.data.includes('Login successful')) {
navigate('/');
};
});
I would like to pass the res.data from this post request to a different React component. Specifically being the Header component so I can display the text from res.data.
This is the structure of the components in my React app
<>
<Header />
<Routes>
<Route path="/" element={<Index />}/>
<Route path="/results/:id" element={<Results />} /> {/* Passes route with ID from input */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register /> } />
<Route path="/users" element={<Users />}/>
<Route path="/products" element={<Products />}/>
<Route path="*" element={<Error />}/>
</Routes>
<Footer />
</>
With Login being the component containing the res.data and Header being the component I would like to send res.data too.
Ill include the back-end for in case their is a method to send this to that component from the back-end but ideally I would like to do it through the front-end perhaps using an axios request.
app.post('/users/validate', (req, res) => {
const email = req.body.email;
const password = req.body.password;
if (email && password) {
//Check all emails against input
db.query(selectEmail, [email], (err, rows) => {
if (err) throw err;
if (rows.length > 0) {
//If password and email match then login
if (HashPassword(password, rows[0].salt) == rows[0].password) { //Checks password and compares to hash
res.send(rows[0].first_name);
}
else {
res.send('Incorrect password');
};
}
else {
res.send('Email or password are incorrect');
};
});
};
});
In a simplified version I want to send the first_name row from my back end to the Header component on my front-end. But I am struggling to understand how to do this because the post request containing first_name is found under is currently being used under the Login component.

How about passing with the state prop in useNavigate - hook as
axios
.post("http://localhost:8080/users/validate", {
email: email,
password: password,
})
.then((res) => {
setError(res.data);
//If validation passed
if (res.data.includes("Login successful")) {
navigate("/", {state: res.data}); // like so
}
});
and In Header component you can get the value using useLocation hook

You should use a state management library like Redux or React Context
With the state management library, you can access your state in all the components of your app.

Related

How can I logout user on route change once JWT token is expired?

I'm using Passport JWT and I want to check JWT token validity to perform a logout if it's already expired. I already made it work on page refresh but not on route change (from navbar, for example). I need to find a way to check it every time any route component is re-rendered, and I know it's very likely that I don't need to do it on every single component. I just don't know how.
Here's the code that's working for page refresh:
On Express
authenticated.js
router.route("/").get((req, res) => {
passport.authenticate("jwt", { session: false }, (err, user, info) => {
if (!user) {
res.status(401);
res.send("Unauthorized")
}
})(req, res);
});
module.exports = router;
On React
AuthContext.js
export const AuthContext = createContext();
function AuthProvider({ children }) {
const [isLoaded, setIsLoaded] = usePersistedState(false, "loaded");
const [user, setUser] = usePersistedState(null, "username");
const [isAuthenticated, setIsAuthenticated] = usePersistedState(
false,
"auth"
);
useEffect(() => {
axios
.get("/authenticated")
.then((response) => {
return;
})
.catch((error) => {
setUser("");
setIsAuthenticated(false);
setIsLoaded(true);
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return (
<div>
{!isLoaded ? (
<h3>Loading...</h3>
) : (
<AuthContext.Provider
value={{ user, setUser, isAuthenticated, setIsAuthenticated }}
>
{children}
</AuthContext.Provider>
)}
</div>
);
}
export default AuthProvider;
index.js
ReactDOM.render(
<AuthProvider>
<App />
</AuthProvider>,
document.getElementById("root")
);
UPDATE
I tried to get it done by adding this useEffect function on my PrivateOutlet.js, which handles the private routes, but it didn't work:
function PrivateOutlet() {
const authContext = useContext(AuthContext);
const location = useLocation();
useEffect(() => {
axios
.get("/authenticated")
.then((response) => {
return;
})
.catch((err) => {
authContext.setIsAuthenticated(false);
authContext.setUser("");
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location]);
return authContext.isAuthenticated ? (
<Outlet />
) : (
<Navigate to="/login" replace state={{ path: location.pathname }} />
);
}
export default PrivateOutlet;
The response is not specific to any platform/language. In general, the authentication provider/service is issuing the token to the client. The token meant for accessing the private resoruces upon validating by resouce server. As you're using the JWT token, so a resouce server/service can validate the token without talking to authentication service or without performing a database look-up. Your server side code always need to validate the token with the incoming requests, which you can do by creating an interceptor for protected requests. In short, you can invoke call from client to validate the token whenever required.
it could be a socket call for the checking through a wrapper component. If the socket returns false, then history.push him into a page where the login is not required
The approach listed on the "update" section from the original question was on the right path, however there was some caveats on my code preventing it to work properly.
I forgot to remove the useEffect() from AuthContext.js after moving it to PrivateOutlet.js, and I also modified the dependency array to include location.pathname instead of just location:
PrivateOutlet.js
function PrivateOutlet() {
const authContext = useContext(AuthContext);
const location = useLocation();
useEffect(() => {
axios
.get("/authenticated")
.then((response) => {
return;
})
.catch((err) => {
authContext.setIsAuthenticated(false);
authContext.setUser("");
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [location.pathname]);
return authContext.isAuthenticated ? (
<Outlet />
) : (
<Navigate to="/login" replace state={{ path: location.pathname }} />
);
}
export default PrivateOutlet;
This was a minor change and probably didn't interfere at all on anything, but in authenticated.js on my Express server I just moved back from using a custom callback to the standard Passport callback, allowing it to handle unauthorized calls by itself:
authenticated.js
router
.route("/")
.get(passport.authenticate("jwt", { session: false }), (req, res) => {
//do some stuff
res.json();
});
module.exports = router;

How to get api's response when posting data?

I'm doing a React web site, with a node.JS REST Api, and actually there is something that I don't know how to do.
When I'm logging in, my React form make a POST request to my Api (considering it's a School Project so I'm working locally, my React app is on port 3000 and my Api is on port 8080), and when I'm submiting, I'm redirected on the response of my Api.
I'd like to be redirected to a page of my React App and receive the response of my Api (when I'm using res.redirect() I have the React page but not the Api response).
Did somebody know how to do it ?
Here is my React Form Component:
class Form extends Component{
constructor(){
super()
this.location = window.location.href
}
render(){
return(
<form action="http://localhost:8080/login" method="post">
<label for="identifier">Mail:</label>
<br/>
<input type="email" id="identifier" name="identifier"/>
<br/><br/>
<label for="password">Password:</label>
<br/>
<input type="password" id="password" name="password"/>
<input id="locator" name="locator" value={this.location} type="hidden"/>
<br/><br/><br/>
<button type="submit">Se connecter</button>
</form>
)
}
}
And here is my Api login route :
app.post('/login', (req, res) => {
console.log(req.body.identifier);
console.log(req.body.password);
client.connect().then(() => {
let newUser = { identifier: req.body.identifier}
return client.db(`${process.env.MONGODB}`).collection(`${process.env.MONGOCOLLECTION}`)
.findOne(newUser)
.then(
result => {
if(result == null){
console.log("No user found");
return res.status(401).json({ error: 'Utilisateur non trouvé !' });
}else{
console.log("User "+result.name+" "+result.firstName+" found");
if(req.body.password !== cryptr.decrypt(result.password)){
console.log("Mot de passe incorrect !");
return res.status(401).json({ error: 'Mot de passe incorrect !' });
}else{
const token = jwt.sign({
id: result._id,
username: result.identifier
}, "my_secret", { expiresIn: '3 hours' })
console.log(token)
return res.json({ access_token: token })
}
}
},
err => res.status(500).json({ err }),
);
})
.then(() => {
console.log("--------------------------------");
})
res.redirect(req.body.locator)
})
You can use XHR requests to handle API calls to server and fetching back data to render in your React component.
Instead of using action in form,
use
<form onSubmit={this.handleSubmit}>
and define a method which handles the submit logic.
async handleSubmit(e) {
const reponse = await
fetch('http://localhost:8080/login', {
method: 'POST',
mode: 'cors', //as your server is on different origin, your server will also have to enable CORS
body: JSON.stringify(formData) //formData will have to be extracted from your input elements before sending to server
})
const data = response.json() //This is the data from your server which you'll get without the need of redirecing.
}
fetch is the default js library to handle XHR request.
There are others as well like axios.

How to stay logged in even after page refresh?

I am developing a fullstack app using React & Node.
The following is the home screen.
After a user logs in, the name of the user is displayed in the navbar and the response from the server (including the JWT) is saved in the local storage as shown in the following pictures:
Now, when I refresh the page the user is logged out. This should not happen because I am sending the token on every request header using axios global defaults as shown in the code snippet below:
frontend/src/App.js
import React from "react";
import { Container } from "react-bootstrap";
import Header from "./components/Header";
import Footer from "./components/Footer";
import Products from "./components/Products";
import { Route, Switch } from "react-router-dom";
import SingleProductView from "./components/SingleProductView";
import Cart from "./components/Cart";
import LoginForm from "./components/LoginForm";
import RegisterForm from "./components/RegisterForm";
import Profile from "./components/Profile";
import Shipping from "./components/Shipping";
import PaymentMethod from "./components/PaymentMethod";
import PlaceOrder from "./components/PlaceOrder";
import axios from "axios";
const {token} = localStorage.getItem("loggedInUser");
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
const App = () => {
return (
<>
<Header />
<main className="py-3">
<Container>
<Switch>
<Route path="/placeorder" component={PlaceOrder} />
<Route path="/payment" component={PaymentMethod} />
<Route path="/shipping" component={Shipping} />
<Route path="/profile" component={Profile} />
<Route path="/register" component={RegisterForm} />
<Route path="/login" component={LoginForm} />
<Route path="/product/:id" component={SingleProductView} />
<Route path="/cart/:id?" component={Cart} />
<Route path="/" component={Products} exact />
</Switch>
</Container>
</main>
<Footer />
</>
);
};
export default App;
What am I doing wrong? I want the user to stay logged in even after page refresh.
Axios will lose its custom (provided by you) default headers settings after page is refreshed.
So, you need to set it again after page refresh (i.e. at App Start). You can do it in a component which always gets mounted before other components do:
In most cases, App component would be the perfect place for that:
// App.jsx
useEffect(() => {
const { token } = localStorage.getItem("loggedInUser");
if (token) {
axios.defaults.headers.common["Authorization"] = `Bearer ${token}`;
}
}, [])
Assuming, you are setting the Token for the first time in localStorage after the Authentication is successful. E.g. in Login component.
Another option could be to write a Request Interceptor for Axios and check if this header exists or not; if not, then set it back from localstorage and let the request proceed.
Using Request Interceptor:
axios.interceptors.request.use(
function (config) {
if (!config.headers.Authorization) {
const { token } = localStorage.getItem('loggedInUser')
if (token) {
axios.defaults.headers.common['Authorization'] = `Bearer ${token}` // this will be used in next requests
config.headers.Authorization = `Bearer ${token}` // this is for current request
}
}
return config
},
function (error) {
return Promise.reject(error)
}
)
Edit:
You are passing token to each AsyncThunkAction. So, all you need to do is initialize the redux state properly. So, in the loginSlice:
function getToken() {
const { token } = localStorage.getItem("loggedInUser");
if (token) {
return { token };
}
return null;
}
const initialState = {
status: "idle",
user: getToken(),
error: null,
};

Getting TypeError: Cannot read property 'prototype' of undefined after modifying form

I am currently working on an application with node.js, express and ReactJs. I am also using sequelize as my ORM. I was working on my form which originally sent nulls to my database and since then I have been stuck on this error for a few days. I am getting the following on my frontend ReactJs:
TypeError: Cannot read property 'prototype' of undefined'
I know this error is very vague and happens for many reasons. For me it happened when I was working on my form.I am using useStates to set my values and using axios to handle the post request.
Where I declared my states:
let [state, setState] = useState({
leadName: "",
excellentLead: ""
});
handleChange(evt) function:Used to handle changes based on my input.
function handleChange(evt) {
const value = evt.target.value;
axios({
method: "post",
url: "http://localhost:5000/add",
data: body,
})
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
setState({
...state,
[evt.target.name]: evt.target.value,
});
Current form(I am using React Bootstrap for styling.):
<Form onSubmit={handleChange}>
<Form.Group controlId="formBasicName">
<Form.Label>Name</Form.Label>
<Form.Control
type="text"
placeholder="Enter Name"
name="leadName"
value={state.leadName}
onChange={handleChange}
/>
</Form.Group>
<Form.Group controlId="formBasicFollowUp">
<Form.Label>
Excellent Lead Worth following up(Yes/No/Maybe)
</Form.Label>
<Form.Control
type="text"
placeholder="Enter Name"
name="excellentLead"
value={state.excellentLead}
onChange={handleChange}
/>
</Form.Group>
<Button variant="primary" type="submit">
Submit
</Button>
</Form>
In my server.js this is my post route:
app.post('/add', function(request, response) {
Information.create({
Name:req.body.leadName,
ExcellentLeadWorthFollowingUp:req.body.excellentLead
}).then(function(Information){
res.json(Information);
})
})
I don't have any issues connecting to the database from the front end and my post method used to send nulls on a button click which wasn't the goal. To add, this error broke my entire program.
Any suggestions, explanations or links would be appreciated!
Since you have fixed your error, I would like to make you aware that you are calling handleChange every time your input changes. This means you are making a POST request every time you type a character in your input.
A better approach would be to only do your post on submit.
import React, { useState } from "react";
const App = () => {
const [data, setData] = useState({ name: "", age: "" });
const handleChange = (e, field) => {
e.preventDefault(); // prevent the default action
// set your data state
setData({
...data,
[field]: e.target.value
});
};
const handleSubmit = (e) => {
e.preventDefault();
// axios post data
console.log(data); // just for showing
};
return (
<div>
<p>Fill in data</p>
<form onSubmit={handleSubmit}>
<input
type="text"
value={data.name}
onChange={(e) => handleChange(e, "name")}
/>
<input
type="text"
value={data.age}
onChange={(e) => handleChange(e, "age")}
/>
<button type="submit">SUBMIT</button>
</form>
</div>
);
};
export default App;
import { response } from 'express';
Was imported and that caused the error this whole time....A classic.

react authentication flow verification

I have some API in nodejs and a React App for client side. I try to create the auth system for my API/backoffice with a jwt token, I use jsonwebtoken for create and verify token on server side but I have some doubt for client side...now on login I save the token on localstorage, then with React-Router "onUpdate" I check if local storage has a token, if not I redirect to login else nothing append, then on my app I append an auth header for each ajax request.
This is my router
export const isLoggedIn = (nextState, replace) => {
console.log(localStorage.getItem('id_token'));
}
<Router history={browserHistory} onUpdate={isLoggedIn} >
<Route path="/" component={App}>
<IndexRoute component={Login.Login} />
<Route path="admin/" component={Dashboard} />
<Route path="admin/tutti" component={Users} />
</Route>
</Router>
Here I login
$.get('/login',credential, function (result) {
localStorage.setItem('id_token', result.token)
});
Generic request:
$.ajax({
url:"/api/users",
type:'GET',
contentType: "application/json",
success:function (result) {},
headers: {"x-access-token": localStorage.getItem('id_token')}
});
is this a correct way to manage the React auth flow?
my doubt is, on isLoggedIn I need to verify the token in some way?
thank you at all!
Do you know Higher Order Components?
Here is an article about HOC: https://medium.com/#franleplant/react-higher-order-components-in-depth-cf9032ee6c3e#.hb4ck2u52
React authentication flow can be written as a HOC.
For example:
import React, { Component, PropTypes } from 'react';
export default function (ComposedComponent) {
class Auth extends Component {
componentWillMount() {
const isLoggedIn = .... // your way to check if current user is logged in
if (!isLoggedIn) {
browserHistory.push('/'); // if not logged in, redirect to your login page
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
return Auth;
}
But I suggest you to use FLUX flow, such as Redux, and store your state in Redux store.
Here is my Redux implementation:
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import { browserHistory } from 'react-router';
export default function (ComposedComponent) {
class Auth extends Component {
componentWillMount() {
if (!this.props.isLoggedIn) {
browserHistory.push('/login');
}
}
componentWillUpdate(nextProps) {
if (!nextProps.isLoggedIn) {
browserHistory.push('/login');
}
}
render() {
return <ComposedComponent {...this.props} />;
}
}
Auth.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
};
function mapStateToProps(state) {
return { isLoggedIn: state.userReducer.isLoggedIn };
}
return connect(mapStateToProps)(Auth);
}
Usage:
import auth from '/path/to/HOC/Auth';
<Router history={browserHistory}>
<Route path="/" component={App}>
<IndexRoute component={Login.Login} />
<Route path="admin/" component={auth(Dashboard)} /> // wrap components supported to be protected
<Route path="admin/tutti" component={auth(Users)} />
</Route>
</Router>

Resources