5
votes

I have server side React/Redux/Express app. React-router v4 provides solution for a server app with Switch and I need to use something to change location from my NavBar component

App

import React, { Component } from 'react'

import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

import { Switch, Route, Redirect, Link} from 'react-router-dom'
import FirstPage from './FirstPage'
import Test from './Test'
import LoginPage from './login/LoginPage'
import NoMatch from '../components/NoMatch'
import NavBar from '../components/NavBar'

import * as loginActions from '../actions/login'

import 'bootstrap/dist/css/bootstrap.css';

class App extends Component {

  render(){
    return (
      <div>
            <NavBar/>

            <h1>EffortTracker v3.0.1</h1>

        <Switch >
            <Route exact path="/" render={loginRedirect(<FirstPage/>)}/>
              <Route path="/first" render={loginRedirect(<FirstPage/>)}/>
              <Route path="/second" render={loginRedirect(<Test/>)}/> />
              <Route path="/login" render={()=><LoginPage {...this.props.login}/>} />
              <Route component={NoMatch}/>
        </Switch>

          <Link to={'/first'}>First</Link>
      </div>
    )
  }
}

const loginRedirect=(component) => {
    if(!isLoggedIn()) return ()=> <Redirect to='login'/>

    return ()=> component
}

const isLoggedIn= ()=>{
    let token = localStorage.getItem('token')

if (token !== null)return false
    else return true

}

const mapStateToProps = state => ({
    login: state.login,
    error: state.error,
    isLoading: state.isLoading,
})

const mapDispatchToProps = dispatch => ({
    loginActions: bindActionCreators(loginActions, dispatch)
})

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(App)

Need to change from NavBar

import React from 'react'
import { Link, NavLink } from 'react-router-dom'

import classnames from 'classnames'

const NavBar =()=> {
    return (
        <nav className={classnames("navbar", "navbar-inverse bg-inverse")}>
            <form className="form-inline">
                <Link to={'/'}>
                    <button className={classnames("btn", "btn-sm", "align-middle", "btn-outline-secondary")}
                            type="button">
                        Smaller button
                    </button>
                </Link>

                <NavLink to='/login'>
                    Login
                </NavLink>
            </form>

        </nav>
    )
  }

export default NavBar

If I navigate it manually from browser url its work just fine but if I click a Link or NavLink url is updated but not the App Switch. Also I have an issue when loginRedirect to /login it does not appear and need to refresh page (possible that this two is related ). How to fix this?

2

2 Answers

3
votes

I think the problem here is with redux .. because it blocks rerendering the components as long as the props didn't change,

This is because connect() implements shouldComponentUpdate by default, assuming that your component will produce the same results given the same props and state.

The best solution to this is to make sure that your components are pure and pass any external state to them via props. This will ensure that your views do not re-render unless they actually need to re-render and will greatly speed up your application.

If that’s not practical for whatever reason (for example, if you’re using a library that depends heavily on React context), you may pass the pure: false option to connect():

function mapStateToProps(state) {
  return { todos: state.todos }
}

export default connect(mapStateToProps, null, null, {
  pure: false
})(TodoApp)

here are links for more explanation:

react-redux troubleshooting section

react-router DOCS

0
votes

If using Redux, the redux connect HOC overrides the shouldComponentUpdate lifecycle method on your component and checks for props and state change this can confuse the React Router. Something like a user clicking a link will not necessarily change the state or props as is, leading to not re-rendering the components in the routeing context.

The documentation for react router states a solution for this problem:

Wrap the component with the withRouter HOC

    import { Route, Switch, withRouter } from 'react-router-dom';
    import { connect } from 'react-redux';

    const Main = (props) => (
    <main>
      <Switch>
        <Route exact path='/' component={SAMPLE_HOME}/>
        <Route path='/dashboard' component={SAMPLE_DASHBOARD}/>    
      </Switch>
    </main>
      )
    export default withRouter(connect()(Main))  

Also, as an enclosing route component will pass props with a location property, you can pass that into the child component and that should achieve the desired behaviour.

https://reacttraining.com/react-router/web/guides/dealing-with-update-blocking