How to use "<Redirect>" inside a function? - reactjs

This is my function so that when a user logs in, it checks the user's password and username and if they are both correct it should redirect to the /note url with the user ID passed through it. I have seen people use it on render(), but I would like to keep my App.jsx app simple, I think that's good practice.
I have tried using the useHistory method, but when the user inputs a data, the user needs to refresh the page manually for the new data to show. If I add a useHistory to refresh the page, it says that the location.state value is undefined.
How should I go about this problem?
Thank you
// Checking to see if current user's log info exists in db
function logIn(e) {
e.preventDefault();
const currentUser = users.find(
(users) => users.username === userInput.username
);
if (!currentUser) {
console.log("The Username was not found. Please try again");
} else {
if (currentUser.password === userInput.password) {
// history.push("./note", { userId: currentUser._id });
return <Redirect to={"/note/" + currentUser._id} />;
} else {
console.log("The password is incorrect");
}
}
}
Here's my routing code
import React from "react";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import NoteDashboard from "./NoteDashboard";
import Home from "./Home";
import CreateAccount from "./CreateAccount";
function App() {
return (
<Router>
<div className="container">
<Switch>
<Route exact path="/" component={Home}></Route>
<Route path="/create" component={CreateAccount}></Route>
<Route path="/note" component={NoteDashboard}></Route>
</Switch>
</div>
</Router>
);
}
export default App;

The only thing (I found) you can do to redirect to a page from function is use ReactDOM.hydrate. Here an example. Example works, but only first time. After that, redirection stops to works. Maybe because codesandbox concats to url the symbol # for some technical reason that I don't know. Try to apply this solution locally an let me know if works more tha one time.

Okay I got it to work, but I don't know if this is good practice or not.
Inside the if statement where I check to see that it is the correct user, I call a function which changes a React Hook from false to true, then in my return statement, I say
{redirect ? <Redirect to={"/note/" + currentUserId} /> : null}
It seems to work fine, everything works. Does this look like a good solution or are there some flaws that I am not seeing.
Thank you for all the help.

try something like this:
state = {
username: '',
app_pass: '',
redirectLink: '',
redirect: false
};
componentDidMount() {
if (window.localStorage.getItem('access_token')) {
this.setState({ redirectLink: '/user/dashboard', redirect: true });
}
}
render() {
const { redirect } = this.state;
const { redirectLink } = this.state;
if (redirect) {
return <Redirect to={redirectLink} />;
}
}

Related

Can 't redirect to homepage after login whit <Redirect>

i 'm traying to use Redirect from react-router-dom 4.3.1.
All the logic i apply into mi app.js and i do a get for the token and if exist i have to redirect the user to the homepage..
Also i try to use history, but i dosn 't work.. where is the problem?
app.js
import { Redirect } from 'react-router-dom'
async componentDidMount() {
try{
const urlToken = new URL(window.location)
const authToken = urlToken.searchParams.get('auth_token')
console.log('MATCH ',authToken)
const tokenUser = await getTokenUser(authToken)
console.log('tokenUser', tokenUser.status)
if(tokenUser.status == 200) {
this.setState({ toHome: true })
}
}catch(error){console.log('NOT')}
}
render(){
if(this.state.toHome == true) {
return <Redirect to='/dashboard' />
}
}
The purpose of componentDidMount is not to return a React element. If you want to redirect the user here, you will have to do it programmatically.
One thing that you have to remember is that the Redirect should be part of the returned JSX in your render method.
You have to create a render method in your component. You can return in also conditionally if you want to check something before redirecting. It would look something like:
render () {
return (
<div>
{(condition) && <Redirect />}
</div>)

Navigate to another page using Material SearchBar

I am using a Material SearchBar in the header of my app, and trying to navigate to a page '/search' on localhost onRequestSearch. The code for the search bar itself is simple:
<SearchBar
onChange = {() => console.log('onChange')}
onRequestSearch = {() => this.setRedirect()}
style = {{ margin: '0 auto', maxWidth:800}}/>
Now, I've tried implementing setRedirect function in a number of ways with no luck. First, I simply tried:
setRedirect() {
this.props.history.push('/search');
}
But this gives an error of Undefined is not an object (evaluating this.props.history). I do have a Route set up for "/search" page in my main app.
I also tried using Redirect:
constructor(props) {
super(props);
this.state = {
redirect = false
}
}
setRedirect() {
this.setState({
redirect: true
}
}
And in the render method:
render() {
if(this.state.redirect) {
return (
<Router>
<div>
<Redirect push to="/search"/>
<Route path="/search" exact strict component={Search}/>
</div>
</Router>
);
}
else {
// return other stuff
}
}
This simply results in the entire page going blank when I type something in the search bar and hit enter. The address in the browser does NOT change to '/search' either.
Not sure what I'm doing wrong. Simply trying to navigate to another page once user enters text in the search bar and hits enter. Any suggestions will be greatly appreciated. Thank you in advance!
Try to add withRouter HoC and then try again with this.props.history.push()
import { withRouter } from 'react-router-dom'
...
export default withRouter(MyComponent);

React Router Redirect Conditional

I'm trying to make a button that only redirects the user to a new page after validation is completed correctly.
Is there a way of doing something like this?
How to I get a Route to be activated inside of a class method?
import validator from './validator';
class Example {
constructor(props) {
super(props)
this.saveAndContinue = thos.saveAndContinue.bind(this)
}
saveAndContinue () {
var valid = validator.validate(this.props.form)
if (valid) {
axios.post('/path')
<Redirect to='/other_tab'>
} else {
validator.showErrors()
}
}
render() {
<button onClick={this.saveAndContinue}>Save and Continue</button>
}
}
As discussed you should have access to the history object via this.props.history as described here.
If you look into the push function this will redirect you to any route you need.
For example:
// Use push, replace, and go to navigate around.
this.props.history.push('/home', { some: 'state' })
https://reacttraining.com/react-router/web/api/history
Like Purgatory said you can do it without using <Redirect />, but otherwise you could make your component a stateful component and then do a conditional render like so
render() {
!this.state.redirect ?
<button onClick={this.saveAndContinue}>Save and Continue</button> :
<Redirect to='/other_tab'>
}
And let the saveAndContinue() change the component state.
saveAndContinue () {
var valid = validator.validate(this.props.form)
if (valid) {
axios.post('/path')
this.setState({redirect: true});
} else {
validator.showErrors()
}
}
When the state changes it would cause a re-render and this time the <Redirect /> would be rendered.
Note: I didn't actually run this code snippet, so it may contain (hopefully minor) errors.

react-router: Not found (404) for dynamic content?

How can react-router properly handle 404 pages for dynamic content in a Universal app?
Let's say I want to display a user page with a route like '/user/:userId'. I would have a config like this:
<Route path="/">
<Route path="user/:userId" component={UserPage} />
<Route path="*" component={NotFound} status={404} />
</Route>
If I request /user/valid-user-id, I get the user page.
If I request /foo, I get a proper 404.
But what if I request /user/invalid-user-id. When fetching the data for the user, I will realize that this user does not exist. So, the correct thing to do seams to be:
Display the 404 page
Return a 404 http code (for server side
rendering)
Keep the url as is (I don't want a redirect)
How do I do that?? It seams like a very standard behaviour. I'm surprised not to find any example...
Edit:
Seams like I'm not the only one to struggle with it. Something like this would help a lot: https://github.com/ReactTraining/react-router/pull/3098
As my app won't go live any time soon, I decided to wait to see what the next react-router version has to offer...
First of create a middleware function for the onEnter callback, so that this is workable for redux promises:
import { Router, Route, browserHistory, createRoutes } from "react-router";
function mixStoreToRoutes(routes) {
return routes && routes.map(route => ({
...route,
childRoutes: mixStoreToRoutes(route.childRoutes),
onEnter: route.onEnter && function (props, replaceState, cb) {
route.onEnter(store.dispatch, props, replaceState)
.then(() => {
cb(null)
})
.catch(cb)
}
}));
}
const rawRoutes = <Route path="/">
<Route path="user/:userId" component={UserPage} onEnter={userResolve.fetchUser} />
<Route path="*" component={NotFound} status={404} />
</Route>
Now in this onEnter function you can work directly with the redux store. So you could dispatch an action that either successes or fails. Example:
function fetch(options) {
return (dispatch) => {
return new Promise((resolve, reject) => {
axios.get('<backend-url>')
.then(res => {
resolve(dispatch({type: `CLIENT_GET_SUCCESS`, payload: res.data}))
})
.catch(error => {
reject(dispatch({type: `CLIENT_GET_FAILED`, payload: error}));
})
}
})
}
}
let userResolve = {
fetchUser: (dispatch, props, replace) => {
return new Promise((next, reject) => {
dispatch(fetch({
user: props.params.user
}))
.then((data) => {
next()
})
.catch((error) => {
next()
})
})
}
}
Whenever the resolve promise now fails, react-router will automatically look for the next component that it could render for this endpoint, which in this case is the 404 component.
So you then wouldn't have to use replaceWith and your URL keeps retained.
If you are not using server side rendering, returning 404 before the page gets rendered would not be possible. You will need to check for the existence of the user somewhere either way (on the server or via AJAX on the client). The first would not be possible without server side rendering.
One viable approach would be to show the 404 page on error of the Promise.
I tried my solution in a project that I am making which uses Server Side Rendering and react-router and it works there, So I'll tell you what I did.
Create a function in which you'll validate an ID. If the ID is valid, Then return the with User page with proper Component, If the ID is invalid then return the with 404 Page.
See the example:
// Routes.jsx
function ValidateID(ID) {
if(ID === GOOD_ID) {
return (
<Route path="/">
<Route path="user/:userId" component={UserPage} />
<Route path="*" component={NotFound} status={404} />
</Route>
);
} else {
return (
<Route path="/">
<Route path="user/:userId" status={404} component={Page404} />
<Route path="*" component={NotFound} status={404} />
</Route>
);
}
// Router.jsx
<Router route={ValidateID(ID)} history={browserHistory}></Router>
This should work with Server Side rendering as it did in my project. It does not uses Redux.
In case of dynamic paths, you can do it like this and you don't have to change the current path.
just import the error404 component and define a property(notfound) in the state to use for conditioning.
import React, { Component } from 'react';
import axios from 'axios';
import Error404 from './Error404';
export default class Details extends Component {
constructor(props) {
super(props)
this.state = {
project: {}, notfound: false
}
}
componentDidMount() {
this.fetchDetails()
}
fetchDetails = () => {
let component = this;
let apiurl = `/restapi/projects/${this.props.match.params.id}`;
axios.get(apiurl).then(function (response) {
component.setState({ project: response.data })
}).catch(function (error) {
component.setState({ notfound: true })
})
}
render() {
let project = this.state.project;
return (
this.state.notfound ? <Error404 /> : (
<div>
{project.title}
</div>
)
)
}
}
I encountered a similar problem while making a blog website. I've been searching for a solution for a while now. I was mapping (using map function) my blog component based on dynamic link.
The initial code snippet was as follows:
import Blog from '../../Components/Blog/Blog.component';
import './BlogPage.styles.scss';
const BlogPage = ({ BlogData, match }) => {
return (
<div className='blog-page'>
{
BlogData.map((item, idx)=>
item.link === match.params.postId?
<Blog
key={idx}
title={item.title}
date={item.date}
image={item.image}
content={item.content}
match={match}
/>
:''
)
}
</div>
)
};
export default BlogPage;
I used a hack where I would use filter function instead of map and store it and then check if it exists (in this case check if length greater than zero for result) and if it does the blog component is rendered with the props for the page else I render the Not Found component (My404Component).
The snippet as follows:
import Blog from '../../Components/Blog/Blog.component';
import My404Component from '../../Components/My404C0mponent/My404Component.component';
import './BlogPage.styles.scss';
const BlogPage = ({ BlogData, match }) => {
const result = BlogData.filter(item => item.link === match.params.postId);
console.log(result);
return (
<div className={result.length>0? 'blog-page': ''}>
{
result.length>0?
<Blog
title={result[0].title}
date={result[0].date}
image={result[0].image}
content={result[0].content}
match={match}
/>
:<My404Component />
}
</div>
)
};
export default BlogPage;
This way the Blog component is not rendered as long as the value of the entered link is not valid as result would be an empty array and it's length would be 0 and instead My404Component would be rendered.
The code is a little raw I havn't refactored it yet.
Hope this helps.

Get the current route name into the component

I am trying not to use : if(window.location) to get access to my current route.
I am working on a code where the router is already defined, and the component is already written. I am trying to access the router information from the component because I need to apply a classname depending on the route.
Here's how the router looks like:
export const createRoutes = (dispatch, getState) => (
<Route component={AppLayout} >
<Route path="/" component={Home} onEnter={(nextState, replaceState) => {
console.log('on Enter / = ', nextState);
nextState.test = 'aaaa'; //can't seem to pass states here....
}} />
then in my component:
render() {
//I want to access my super router or location . in fact
// I just want to know if currentURl === '/'
//and after reading the react-router doc 5 times - my head hurts
// but I still hope to get an answer here before putting a window.location
}
`
Depending on which version of React Router you're using you have access to different objects available on the context of a component.
MyComponent.contextTypes = {
location: React.PropTypes.object
}
which will enable you to do the following
render() {
if (this.context.location.pathname === '/') {
return <h1>Index</h1>;
} else {
return <h2>Not index</h2>;
}
}

Resources