How push route to another path in react-redux connected component - javascript

i wan't to make conditional routing in my component. I'm using react-redux. I have tried different things like but getting undefined error.
this.props.history.push('/pathname')
so what i wan't to do, on component will mount i'm checking some boolean value from my redux state if this value will return false i want to redirect user to another route.
my component is:
import React, {Component} from 'react'
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux'
class SomeComponent extends Component {
componentWillMount() {
this.props.someFunc()
if ( this.props.userLogin[0].isUserLogin === false ) {
/// here i want to redirect to another route
}
}
render() {
return(
<div>Some Content</div>
)
}
}
const mapStateToProps = state => ({
userLogin: state.UserLogin,
})
function mapDispatchToProps(dispatch) {
return {
someFunc: bindActionCreators(someAction, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SomeComponent)
Like in example i want to redirect in component will mount user to another path. How i can do that?

You need Redirect
import {Redirect} from 'react-router-dom';
{boolFlag && <Redirect to="/hello" />} // put this in render

Do you mean "history is undefined"?
You can get history from parent component

Related

this.props.match.params passed into child component after authorisation

I have recently started building a big project on React using also a Firebase with authentication and I cannot quite understand the relation between the react-router-dom links and React components.
I am struggling with getting the
this.props.match.params // which is going to be 2018 / 2019 / 2020... etc
in the component, which renders as a dynamic route (like unique post component).
I have tried to use only a simple class component and this works but the problem is, without the authentication everyone can access this admin route and everyone would be allowed to edit and delete data there. I want it to be accessed only by authenticated users. (Admins)
So this is how my piece of code looks like:
Main component: (where the link is)
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
class SeasonBox extends Component {
render() {
return (
<Link className='seasonbox' to={`/adminseason/${this.props.season}`}>
<p className='seasonbox__season'>{this.props.season}/{this.props.season+1}</p>
</Link>
)
}
}
export default SeasonBox;
And the component that renders after the link is clicked:
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withAuthorisation } from '../Session'
import { withFirebase } from '../Firebase'
const AdminMatchesBox = ({authUser}) => (
<div>{authUser ? <AdminMatchesBoxAuth /> : <AdminMatchesBoxNonAuth />} </div>
)
class AdminMatchesBoxAuth extends Component {
render() {
return (
<div>
Hey I am the season {this.props.match.params}!
<Link to={'/adminmatches'}>Wróć</Link>
</div>
)
}
}
const AdminMatchesBoxNonAuth = () => (
<div>
<h1>You do not have permission to visit this page.</h1>
</div>
)
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
const condition = authUser => !!authUser
export default compose(withAuthorisation(condition), connect(mapStateToProps),withFirebase)(AdminMatchesBox);
So if I don't use authorisation, and I use only a single class component I can get this.props.match.params -> which is the id of the website and I need it to access data from the database.
However, I want it to not be visible by not logged users and I had to process it through the authorisation process.
I am receiving an error
Cannot read property 'params' of undefined.
I have no clue how to pass match.params into the AdminMatchesBoxAuth component.
Could anyone advice?
By wrapping withRouter you able to access params
Try this
import { withRouter } from "react-router";
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import { connect } from 'react-redux'
import { compose } from 'recompose'
import { withAuthorisation } from '../Session'
import { withFirebase } from '../Firebase'
const AdminMatchesBox = ({authUser}) => (
<div>{authUser ? <AdminMatchesBoxAuth /> : <AdminMatchesBoxNonAuth />} </div>
)
class AdminMatchesBoxAuth extends Component {
constructor (props){
super(props)
}
render() {
return (
<div>
Hey I am the season {this.props.match.params}!
<Link to={'/adminmatches'}>Wróć</Link>
</div>
)
}
}
const AdminMatchesBoxNonAuth = () => (
<div>
<h1>You do not have permission to visit this page.</h1>
</div>
)
const mapStateToProps = state => ({
authUser: state.sessionState.authUser
});
const condition = authUser => !!authUser
export default compose(withRouter, withAuthorisation(condition), connect(mapStateToProps),withFirebase)(AdminMatchesBox)

Pass state from App.js to React Router component

I have a state in App.js that gets populated with animal data returned by an API. This data is accessible with this.state.animals on the component that is loaded, but my problem is when changing routes to /animals, I can no longer call this.state.animals. It's as if I no longer have access or the data is gone. How can I pass the state to any component when I navigate to them?
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route, NavLink } from 'react-router-dom'
import Animals from './components/Animals'
class App extends Component {
state = {
animals: []
}
componentDidMount() {
fetch('animals api path here')
.then(result => result.json())
.then((data) => {
this.setState({ animals: data })
})
.catch(console.log)
}
render() {
return (
<Router>
<NavLink to="/animals">animals</NavLink>
<Route exact path="/animals" render={()=> <Animals parentState={this.state.animals} />} />
</Router>
);
}
}
export default App
components/Animals.js
import React, { Component } from 'react'
class Animals extends Component {
render() {
console.log(this.state.animals)
return (
<h2>Animals</h2>
//<div>I would loop through and list the animals if I could...</div>
)
}
}
export default Animals
In this example if you want to access animal data, it would be {this.props.parentState} inside fo the animal component
Your Route declaration is defined outside the context of the router. When using React-Router, all your components should have among their ancestors a . Usually, is the top-level component rendered in (at least if you are not using other libraries that need root-context definition, like Redux).

Redux not working with React-router

I'm using the following:
react v16.2.0,
react-redux v5.0.7,
react-router-dom v4.2.2,
redux v3.7.2
What I am trying to achieve is to update some props from the Auth component, and when the user navigates to /user (Which loads the Userpage component), the modified props should be displayed.
Here is a simplified version of my code:
In App.js:
import React, { Component } from 'react';
import { Provider } from 'react-redux';
import Store from './components/Store';
import Home from './components/Home';
import Auth from './components/auth';
import Userpage from './components/Userpage';
import { BrowserRouter as Router, Route } from 'react-router-dom';
class App extends Component {
render() {
return (
<Provider store={Store}>
<Router>
<div>
<Route exact path="/" component={Home}/>
<Route path="/login" component={Auth}/>
<Route path="/user" component={Userpage}/>
</div>
</Router>
</Provider>
);
}
}
export default App;
In Store.js:
import { createStore } from 'redux';
const reducer = (state,action) => {
if(action.type == 'TEST'){
return Object.assign({},state,{test:action.payload.test});
} else
return state;
}
export default createStore(reducer,{
test:'DOES NOT WORK',
})
In Auth.js :
import React from 'react';
import { connect } from 'react-redux';
import Userpage from './Userpage';
class Auth extends React.Component {
componentWillMount() {
this.props.update('TEST',{test:'WORKS'});
}
render() {
return(
<div>
<Userpage/>
</div>
);
}
}
export default connect(
(store) => {
return store;
},
(dispatch) => {
return {
update:(dispatchType, dispatchPayload) => {
dispatch({type:dispatchType,payload:dispatchPayload});
}
}
}
)(Auth);
In Userpage.js:
import React, { Component } from 'react';
import { connect } from 'react-redux';
class Userpage extends Component{
componentDidMount(){
console.log(this.props.test);
}
render(){
return null;
}
}
export default connect(
(store) => {
return store;
}
)(Userpage);
Now, when I navigate to /login, the store is updated and test is set to "WORKS". But if I navigate to /userpage, console.log(this.props.test) prints "DOES NOT WORK", instead of the updated prop.
Just to test, I included Userpage in the render function of Auth and it console logs "WORKS" correctly.
So why is the redux store apparently being reset to default values on navigation to another page using react-router? How can I fix this?
Found the solution. Apparently the whole application re-renders when you manually navigate (by typing in the URL in the browser), hence resetting the redux store.
Using Link as mentioned in react-router docs or redirecting using this.props.history.push('/user') works without any issues.

Unidirectional Data Flow in React with MobX

I'm trying to setup a project architecture using MobX and React and was wondering if doing this following would be considered "not bad". I don't want this question to end up being another "this is a matter of personal preference and so this question doesn't belong here... We can all agree that some things really are bad.
So I'm thinking of only having a single Store.js file that looks something like this:
import { observable, action, useStrict } from 'mobx';
useStrict(true);
export const state = observable({
title: ''
});
export const actions = {
setTitle: action((title) => {
state.title = title;
})
};
Note: that all application state will be in state, there will only be a single store.
I then use state in my root component a.k.a App.js like so:
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { state } from './Store';
import DisplayTitle from './components/DisplayTitle/DisplayTitle';
import SetTitle from './components/SetTitle/SetTitle';
class App extends Component {
render() {
return (
<div className="App">
<DisplayTitle title={state.title}/>
<SetTitle />
</div>
);
}
}
export default observer(App);
I'll obviously have a whole bunch of components in my app, but none of the components will ever read state directly from Store.js. Only my App.js will import the state and pass it down the component tree.
Another note: I'm not so sure anymore why other components can't read the state directly from Store.js...
This is the case with the DisplayTitle component:
import React, { Component } from 'react';
class DisplayTitle extends Component {
render () {
return (
<h1>{this.props.title}</h1>
);
}
}
export default DisplayTitle;
But, even though no other components can directly import state (except App.js), any component can import actions from Store.js in order to mutate the state.
For example in the SetTitle component:
import React, { Component } from 'react';
import { actions } from './../../Store';
class SetTitle extends Component {
updateTitle (e) {
actions.setTitle(e.currentTarget.value);
}
render () {
return (
<input onChange={this.updateTitle} type='text'/>
);
}
}
export default SetTitle;
Are there any flaws or other obvious reasons why this approach wouldn't be the best route to go? I'd love any and all feedback!
you are missing a few things
at root level:
import { Provider } from 'mobx-react'
...
<Provider state={state}>
<Other stuff />
</Provider>
At component level:
import { inject } from 'mobx-react'
#inject('state')
class Foo ... {
handleClick(){
this.props.state.setTitle('foo')
}
render(){
return <div onClick={() => this.handleClick()}>{this.props.state.title}</div>
}
}
You can stick only the interface actions={actions} in your provider and inject that, ensuring children can call your API methods to mutate state and have it flow from bottom up. Though if you were to mutate it direct, no side effects will happen because all components willReact and update in your render tree - flux is cleaner to reason about.

Modal dialog auth with react-router

I have react/redux/react-router application with public and private parts.
Login and register form are shown as modal dialogs and don't have their own routes.
Desired flow:
user clicks on link -> modal dialog is shown on current page -> in case of successful auth transition to linked page, else leave user on current page.
If there is no current page-show index page and continue with the flow
I'm tried to archive this using onEnter hook, but as far as i can see transition happens before hook is executed. If I try to use history.goBack() it's causes rerendering of page and looks nasty.
Is there any way to solve this problem without unnecessary redirects and extra render calls?
OK - I think I came up with a way to handle this that covers all the corner cases. It does require that you have some way to access application state from almost any component though. I am using Redux for that. This also assumes that login, register, etc do NOT have routes.
What I did was create two 'wrapper' components. The first one wraps any insecure routes and stores the location to a state value so that we always have a reference to the last insecure route...
import { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { setRoute } from './redux/client/ducks/auth';
function mapDispatchToProps(dispatch) {
return {
setRoute: bindActionCreators(setRoute, dispatch)
};
}
#connect(null, mapDispatchToProps)
export default class InsecureWrapper extends Component {
componentDidMount() {
const { location, setRoute } = this.props;
setRoute(location.pathname);
}
render() {
return (
<div>
{this.props.children}
</div>
)
}
}
The other one wraps all secure routes. It displays the login dialog (can also toggle back and forth between login and register or whatever) and prevents the content from being displayed unless logged into the app...
import { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './redux/client/ducks/auth';
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return {
authActions: bindActionCreators(authActions, dispatch)
};
}
#connect(
mapStateToProps,
mapDispatchToProps
)
export default class SecureWrapper extends Component {
componentDidMount() {
const { auth, authActions } = this.props;
//see if user and if not prompt for login
if(!auth.loggedIn) {
authActions.openLogin();
}
}
//close any open dialogs in case the user hit browser back or whatever
componentWillUnmount() {
const { authActions } = this.props;
authActions.resetAuthDialogs();
}
render() {
const { auth, children } = this.props;
return (
<div className="container">
{auth.loggedIn &&
{children} ||
<span>YOU MUST BE LOGGED IN TO VIEW THIS AREA!</span>
}
</div>
);
}
}
Then in the routes, just wrap them in the wrappers as needed...
import App from './containers/App';
import Dashboard from './containers/Dashboard';
import Secure from './containers/Secure';
import AuthWrapper from './common/client/components/AuthWrapper';
import InsecureWrapper from './common/client/components/InsecureWrapper';
export default [
{path: '/', component: App, //common header or whatever can go here
childRoutes: [
{component: InsecureWrapper,
childRoutes: [ //<- ***INSECURE ROUTES ARE CHILDREN HERE***
{path: 'dashboard', component: Dashboard}
]
},
{component: SecureWrapper,
//***SECURE ROUTES ARE CHILDREN HERE***
childRoutes: [
{path: 'secure', component:Secure}
]
}
]}
]
Last but not least... in your Dialogs, you need to handle the cancel by pushing (or replacing) the location to the saved state value. And of course on successful login, just close them...
import { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import * as authActions from './redux/client/ducks/auth';
import LoginDialog from './common/client/components/dialogs/LoginDialog';
import RegisterDialog from './common/client/components/dialogs/RegisterDialog';
// this could also be replacePath if you wanted to overwrite the history
import { pushPath } from 'redux-simple-router';
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return {
authActions: bindActionCreators(authActions, dispatch),
pushPath: bindActionCreators(pushPath, dispatch),
};
}
#connect(
mapStateToProps,
mapDispatchToProps
)
export default class AuthContainer extends Component {
_handleLoginCancel = (e) => {
const { auth, authActions, pushPath } = this.props;
pushPath(auth.prevRoute); // from our saved state value
authActions.closeLogin();
};
_handleLoginSubmit = (e) => {
const { authActions } = this.props;
// do whatever you need to login here
authActions.closeLogin();
};
render() {
const { auth } = this.props;
return (
<div>
<LoginDialog
open={auth.showLogin}
handleCancel={this._handleLoginCancel}
handleSubmit={this._handleLoginSubmit}
submitLabel="Login"
/>
...
</div>
)
}
}
I am obviously using ES6, Babel, and webpack... but the principles should apply without them as should not using Redux (you could store the prev route in local storage or something). Also I left out some of the intermediate components that pass props down for brevity.
Some of these could be functional components, but I left them full to show more detail. There is also some room for improvement by abstracting some of this, but again I left it redundant to show more detail. Hope this helps!

Categories