'You tried to redirect to the same route you're currently on: "/"' when using a Redirect component with state - node.js

I'm trying to create a small demo application with two pages, one and two. The user may navigate from page one to page two by pressing a button, but only if the location objects' state in the second page contains a key attribute. If it does not, then the user is redirected to one.
The problem I'm encountering is that when using a to object to redirect from one and pass state to two, React Router warns:
You tried to redirect to the same route you're currently on: "/"
This doesn't make sense to me, because I'm attempting to redirect the user from /one to /two, not / to /two.
App.js
import React, { Component } from 'react';
import './App.css';
import { NavLink, Redirect, Route, BrowserRouter as Router, Switch } from 'react-router-dom';
const App = () => (
<Router>
<div className="App">
<ul>
<li>
<NavLink to="/one">One</NavLink>
</li>
<li>
<NavLink to="/two">Two</NavLink>
</li>
</ul>
<Switch>
<Route path="/one" component={One} />
<Route path="/two" component={Two} />
</Switch>
</div>
</Router>
);
class One extends Component {
constructor(props) {
super(props);
this.state = {
shouldRedirect: false,
};
this.redirect = this.redirect.bind(this);
}
redirect() {
this.setState({
shouldRedirect: true,
});
}
render() {
const { shouldRedirect } = this.state;
if (shouldRedirect) {
return (
// Replacing the below Redirect with the following corrects the error,
// but then I'm unable to pass state to the second page.
// <Redirect to="/two" />
<Redirect
to={{
pathName: '/two',
state: {
key: 'Some data.',
},
}}
/>
);
}
return (
<div>
<h3>This is the first page.</h3>
<button type="button" onClick={this.redirect}>Click me to go to page two.</button>
</div>
);
}
}
class Two extends Component {
constructor(props) {
super(props);
this.state = {
shouldRedirect: false,
};
this.redirect = this.redirect.bind(this);
}
componentWillMount() {
const { location } = this.props;
if (location.state && location.state.key) {
const { key } = location.state;
this.key = key;
} else {
this.redirect();
}
}
redirect() {
this.setState({
shouldRedirect: true,
});
}
render() {
const { shouldRedirect } = this.state;
if (shouldRedirect) {
return (
<div>
<p>You&apos;re being redirected to the first page because a key was not provided.</p>
<Redirect to="/one" />
</div>
);
}
return (
<div>
<h3>This is the second page.</h3>
<p>The key you provided was "{this.key}".</p>
</div>
);
}
}
export default App;
App.css
.App {
text-align: center;
}

You are passing pathName instead of pathname. This should fix the issue.
Working example on code sandbox.
<Redirect
to={{
pathname: "/two",
state: {
key: "Some data."
}
}}
/>

You should not use the <Redirect> as an another route in the <switch>. It should be condition driver. Use the below link for the reference.
https://reacttraining.com/react-router/web/example/auth-workflow

Related

React Component does not update until refresh

I have the following code:
In App.js:
class App extends React.Component {
constructor() {
super();
this.state = {
loggedStatus: {
username: undefined,
isLoggedIn: undefined,
}
}
}
componentDidMount() {
let username = undefined;
let isLoggedIn = undefined;
if (localStorage.getItem("token")) {
fetch("https://localhost:8000/user", {
method: "POST",
headers: {
"Authorization": `Bearer ${localStorage.getItem("token")}`
}
}).then(response => response.json()).then(response => {
if (response.success) {
username = response.username;
isLoggedIn = true;
} else {
username = undefined;
isLoggedIn = false;
localStorage.removeItem("token");
}
this.setState({
loggedStatus: {
username: username,
isLoggedIn: isLoggedIn
}
})
})
}
}
render() {
return (
<div className="App">
<Router>
<Navbar loggedStatus={this.state.loggedStatus}/>
<Switch>
<Route path="/register">
<RegisterForm />
</Route>
<Route path="/login">
<LoginForm />
</Route>
</Switch>
</Router>
</div>
)
}
}
and in Navbar.js:
logout = () => {
localStorage.removeItem("token");
}
render() {
return (
<nav className="Navbar">
{this.props.loggedStatus.isLoggedIn ?
<>
<ul className="Navbar-list">
<li className="Navbar-item">
<span className="Navbar-greeting">Hello, {this.props.loggedStatus.username}</span>
</li>
<li className="Navbar-item">
<button className="Navbar-logoutBtn" onClick={this.logout}>Sign Out</button>
</li>
</ul>
</>
:
<>
<ul className="Navbar-list">
<li className="Navbar-item">
<Link to="/register" className="Navbar-link">Sign Up</Link>
</li>
<li className="Navbar-item">
<Link to="/login" className="Navbar-link">Log In</Link>
</li>
</ul>
</>
}
</nav>
)
}
The problem I'm having is I would like my navbar component to update when either the user logs in, or logs out. With my current code, I have to refresh the page in order for it to update. I've been messing around with things with no luck. I understand that componentDidMount is only called once through the entire process, which is why setState is only called upon refresh.
Edit: Login.
fetch(`${this.apiURL}/user/login`, {
method: "POST",
body: JSON.stringify(user),
headers: {
"Accept": "application/json",
"Content-Type": "application/json"
}
})
.then(response => response.json())
.then(response => {
if (response.success) {
status = {
statusMsg: <p className="LoginForm-statusMsg">{response.statusMsg}</p>
}
localStorage.setItem("token", response.token);
this.setState({
status: status
}, () => setTimeout(() => {
this.props.history.push("/");
}, 5000));
} else {
status = {
statusMsg: <p className="LoginForm-statusMsg">{response.statusMsg}</p>
}
this.setState({
status : status
})
}
});
}
}
setState({}) always forces to re-render. (unless you return false in: shouldComponentUpdate(nextProps, nextState)) You can check this by putting a console log in
componentDidUpdate(prevProps, prevState) {
console.log("Component did update")
}
It's not clear what your JobsScreenTabs component consists of but make sure that for changes you expect to happen inside the JobsScreenTabs component it actually changes its state. Pass properties from your WorkshopJobsScreen component or make changes directly in the JobsScreenTabs component.
Also important:
Using State Correctly
There are three things you should know about setState().
Do Not Modify State Directly
For example, this will not re-render a component:
// Wrong
this.state.comment = 'Hello';
Instead, use setState():
// Correct
this.setState({comment: 'Hello'});
React may batch multiple setState() calls into a single update for performance.
Because this.props and this.state may be updated asynchronously, you should not rely on their values for calculating the next state.
Neither parent nor child components can know if a certain component is stateful or stateless, and they shouldn’t care whether it is defined as a function or a class.
This is why state is often called local or encapsulated. It is not accessible to any component other than the one that owns and sets it.
So if you wish to make changes in your component make sure to manipulate the state of the correct component.
Read more about React lifecycles at: https://reactjs.org/docs/state-and-lifecycle.html
So just some general info. Answer starts here:
You can pass your state in props from app js to navbar component through the route:
let loggedStatus = {
username: undefined,
isLoggedIn: false
}
<Route path="/" render={(props) => <NavBar props={loggedStatus} {...props} /> } exact />
In NavBar you can access it:
export class NavBar extends Component {
constructor(props) {
super(props);
console.log(props)
this.state = {
isLoggedIn: this.props.loggedStatus.isLoggedIn
As I'm not aware of the login flow you are following. But as far as what I understood. The code below should match your requirement.
In NavBar.js
logout = () => {
this.props.handleLogout()
}
In app.js
handleLogout = () => {
localStorage.removeItem("token");
this.setState({
loggedStatus: {
username: undefined,
isLoggedIn: false
}
})
}
render(){
return(
..
..
<Navbar loggedStatus={this.state.loggedStatus} handleLogout={this.handleLogout}/>
)
}
The state changes so it will re-render the component.
Hope this helps you.

React: How to update the DOM with API results

My goal is to take the response from the Google API perspective and display the value into a div within the DOM.
Following a tutorial : https://medium.com/swlh/combat-toxicity-online-with-the-perspective-api-and-react-f090f1727374
Form is completed and works. I can see my response in the console. I can even store the response into an object, array, or simply extract the values.
The issue is I am struggling to write the values to the DOM even though I have it saved..
In my class is where I handle all the API work
class App extends React.Component {
handleSubmit = comment => {
axios
.post(PERSPECTIVE_API_URL, {
comment: {
text: comment
},
languages: ["en"],
requestedAttributes: {
TOXICITY: {},
INSULT: {},
FLIRTATION: {},
THREAT: {}
}
})
.then(res => {
myResponse= res.data; //redundant
apiResponse.push(myResponse);//pushed api response into an object array
console.log(res.data); //json response
console.log(apiResponse);
PrintRes(); //save the values for the API for later use
})
.catch(() => {
// The perspective request failed, put some defensive logic here!
});
};
render() {
const {flirty,insulting,threatening,toxic}=this.props
console.log(flirty); //returns undefined, makes sense upon initialization but does not update after PrintRes()
return (
<div className="App">
<h1>Please leave a comment </h1>
<CommentForm onSubmit={this.handleSubmit} />
</div>
);
}
}
When I receive a response from the API I use my own function to store the data, for use later, the intention being to write the results into a div for my page
export const PrintRes=() =>{
// apiResponse.forEach(parseToxins);
// myResponse=JSON.stringify(myResponse);
for (var i = 0; i < apiResponse.length; i++) {
a=apiResponse[i].attributeScores.FLIRTATION.summaryScore.value;
b=apiResponse[i].attributeScores.INSULT.summaryScore.value;
c=apiResponse[i].attributeScores.THREAT.summaryScore.value;
d=apiResponse[i].attributeScores.TOXICITY.summaryScore.value;
}
console.log("hell0");//did this function run
// render(){ cant enclose the return in the render() because I get an error on the { , not sure why
return(
<section>
<div>
<p>
Your comment is:
Flirty: {flirty}
</p>
</div>
<div>
<p>
Your comment is:
insulting: {insulting}
</p>
</div>
<div>
<p>
Your comment is:
threatening: {threatening}
</p>
</div>
<div>
<p>
Your comment is:
toxic: {toxic}
</p>
</div>
</section>
);
}
Variables and imports at the top
import React from "react";
//needed to make a POST request to the API
import axios from "axios";
import CommentForm from "../components/CommentForm";
var myResponse;
var apiResponse= [];
let a,b,c,d;
let moods = {
flirty: a,
insulting:b,
threatening:c,
toxic:d
}
If I understand correctly You need to create a state where you store data from api.
States in react works like realtime stores to refresh DOM when something change. this is an example to use it
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
apiData: undefined
};
}
fetchData() {
this.setState({
apiData: "Set result"
});
}
render() {
const { apiData } = this.state;
return (
<div>
<button onClick={this.fetchData.bind(this)}>FetchData</button>
<h3>Result</h3>
<p>{apiData || "Nothing yet"}</p>
</div>
);
}
}
you can check it here: https://codesandbox.io/s/suspicious-cloud-l1m4x
For more info about states in react look at this:
https://es.reactjs.org/docs/react-component.html#setstate

How to effectively protect routes in combination with react-router and passport on the backend

I have React and Node.js with passport.js on the backend which implements my app auth. My react makes a call to my backend and fetches the authorized user via action reducer. Everything works fine but there is a problem with the route guards. This is how I am protecting the routes if the user is not logged in
if(!this.props.auth) return
The problem is when the user is logged in, if page is refreshed, the code above executes faster than mapStateToProps returns the authorized user and the loggedIn user is redirected to the index page. This is bad user experience. Please help me how to resolve this issue and I would appreciate help and advice.
I think what I need to do is to ensure that store is updated first before DOM is rendered but I am not sure how to do it.
Here is dashboard
class Dashboard extends Component {
render() {
if(!this.props.auth) return <Redirect to='/' />
if (!this.props.auth.googleUsername) {
return <div className='container'> Loading ... </div>;
} else {
return (
<div className='container' style={{ margin: '10px 10px' }}>
{this.props.auth.googleUsername}
</div>
);
}
function mapStateToProps({auth}) {
return {auth};
}
export default connect(mapStateToProps)(Dashboard);
Here is App.js
import { connect } from 'react-redux';
import { fetchUser } from './store/actions/index';
import Home from './components/layout/Home';
import Dashboard from './components/layout/Dashboard';
class App extends Component {
componentDidMount() {
this.props.fetchUser();
}
render() {
return (
<div>
<BrowserRouter>
<div>
<Header />
<Switch>
<Route exact path='/' component={Home} />
<Route path='/dashboard' component={Dashboard} />
</Switch>
</div>
</BrowserRouter>
</div>
);
}
}
export default connect(null,{ fetchUser })(App)
Action reducer
import axios from 'axios';
import { FETCH_USER } from './types';
export const fetchUser = () => async dispatch => {
const res = await axios.get('/api/current_user');
dispatch({ type: FETCH_USER, payload: res.data });
};
Auth Reducer
import { FETCH_USER } from '../actions/types';
export default function(state = false, action) {
switch (action.type) {
case FETCH_USER:
return action.payload;
default:
return state;
}
}
For those who has this issue, I managed to solve the probable. The issue was that I need to persist redux store across my app. I used a third party library called 'redux-persist'
Here is the set I used in my index.js
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import rootReducer from './store/reducers/rootReducer';
import thunk from 'redux-thunk';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import { PersistGate } from 'redux-persist/integration/react';
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
const store = createStore(persistedReducer, applyMiddleware(thunk));
const persistor = persistStore(store);
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root'));

Display response object from GET request on backend in react component

I am still figuring React out and have a question. I want to display some data that I am getting back from my mLab database. When I make the request in Postman to test request i get back the object full of data and now I want to display that data in my component.
Backend/server.js:
//this is tested and works in postman
app.get('/logs', function(req, res) {
user: req.user;
res.json();
});
React action:
export const GET_DATA_SUCCESS = 'GET_DATA_SUCCESS';
export const GET_DATA_TRIGGERED = 'GET_DATA_TRIGGERED';
export const GET_DATA_FAILURE = 'GET_DATA_FAILURE';
export function getData() {
const promise = fetch('http://localhost:8080/logs');
return {
onRequest: GET_DATA_TRIGGERED,
onSuccess: GET_DATA_SUCCESS,
onFailure: GET_DATA_FAILURE,
promise,
};
}
Component where I want to display:
import React from 'react';
import {Router, Route, Link, Redirect, withRouter} from 'react-router-dom';
import { getData } from '../actions/memory';
import { connect } from 'react-redux';
export class oldMemory extends React.Component {
oldSearch(e) {
e.preventDefault();
this.props.getData();
}
render() {
return(
<div className="old-info">
<Link to="/main"><h3 className="title-journey">Travel Journal</h3></Link>
<h4>Retrieve a Memory</h4>
<p className="get-info">Look back on an old place you have visited and
reminisce.</p>
<input className="search" type="text" name="search" placeholder="Search"
/>
<button onClick={this.oldSearch.bind(this)}>Get</button>
// would like data to show up here
</div>
)
}
}
export default connect(null, { getData })(oldMemory)
I would use a state to store the data and set the state after the getData promise is resolved. Then, in the render method, i map the state data to div elements and display them in the the component.
// I assume your result of get Data is an array of
// objects {id: number,date: string, memory: string}
// and that getData is a promise
class OldMemory extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
}
}
oldSearch = (e) => {
e.preventDefault();
this.props.getData().then(data => {
// if data is null, or undefined set it to an empty array
this.setState({ data: data || [] });
})
}
render() {
// build data to div elements for display
const memories = this.state.data.map(d => <div>{d.date} - {d.memory}</div>)
return(
<div className="old-info">
<Link to="/main"><h3 className="title-journey">Travel Journal</h3></Link>
<h4>Retrieve a Memory</h4>
<p className="get-info">Look back on an old place you have visited and
reminisce.</p>
<input className="search" type="text" name="search" placeholder="Search"
/>
<button onClick={this.oldSearch}>Get</button>
// would like data to show up here
<div id="display-data">
{ memories }
</div>
</div>
</div>
);
}
}
export default connect(null, { getData })(OldMemory)

React.js, onclick handler not changing state of the component

I am developing an app using node.js and react.js. I am trying to change the state of a component using an onclickhandler. The state of the component is not changing once it is rendered on the browser. However , if I try to do an alert in the onclickhandler its working.What might be the issue?
Here is the component that I'm trying to render :
var Server = module.exports = React.createClass({
getInitialState: function() {
return {
readOnly:"readOnly",
}
},
onButtonClick: function() {
this.setState({readOnly: ""});
},
render: function render() {
return (
<Layout {...this.props}>
<div id='index'>
<h1>Hello {this.props.name}!</h1>
<button onClick={this.onButtonClick}>Click Me</button>
<input readOnly={this.state.readOnly} />
<br/>
<a href='/'>Click to go to an react-router rendered view</a>
</div>
</Layout>
);
}
});
You need to set readOnly true or fasle.
onButtonClick: function() {
this.setState({readOnly: true or false});
}
It should work.
Here is a way you could add or remove an attribute to an element
var Example = React.createClass({
getInitialState: function () {
return {
readOnly: true,
}
},
onButtonClick: function () {
this.setState({readOnly: !this.state.readOnly});
},
render: function render() {
return (
<div {...this.props}>
<div id='index'>
<h1>Hello {this.props.name}!</h1>
<button onClick={this.onButtonClick}>Click Me</button>
<input {...(this.state.readOnly) ? {readOnly: 'readOnly'} : {}} />
<br/>
<a href='/'>Click to go to an react-router rendered view</a>
</div>
</div>
);
}
});
In this answer input value is editable only if u click the button otherwise the value of the input is not editable. This is one of the most simple way to achieve this...
import React from 'react';
import ReactDOM from 'react-dom';
class Sample extends React.Component{
constructor(props){
super(props);
this.state={
inputValue:"praveen"
}
this.valueChange = this.valueChange.bind(this);
}
valueChange(e){
this.setState({inputValue: e.target.value})
}
render(){
return(
<div>
<input type="text" value={this.state.inputValue } />
<button type="button" onClick={this.valueChange}>Edit</button>
</div>
)
}
}
export default Sample;
ReactDOM.render(
<Sample />,
document.getElementById('root')
);

Resources