Search code examples
reactjsreact-router-domconnected-react-router

Navbar is not re-rendering on router location change


I want to display userName and changed the button text to 'logout' from 'login' in Navbar(nav component) when user is logged in. I tired to re-render the Nav Component by pushing the router history location to homepage on user logged in.

this.props.history.push('/')

Then User can successfully log in, user data is saved in localStorage and the router location is changed. data is saved in localStorage, but the component is not re-rendering.

Here is what I have tried.

  1. tried to downgrade 'connected-react-router' to 'v6.0.0' but it threw me some errors. so put it back to the latest version '^6.4.0'.

  2. tried to add Navbar code in App.js which is the parents component. But the app.js itself is not re-rendering either on user logged in.

app.js

import React from 'react';
import { connect } from 'react-redux';
/* --- Components --- */
import Nav from './src/components/nav';
import Loader from './src/shared/loader';
import './styles/main.scss';

const FlashMessagesContainer = Loader({
  loader: () =>
    import('./src/shared/flassMessagesContainer' /* webpackChunkName: 'FlashMessagesContainer' */),
});

const App = (props, { isOnModal }) => (
  <div id="app">
    <Nav />
    {!isOnModal && (
      <div className="flex justify-center">
        <FlashMessagesContainer />
      </div>
    )}
    {props.children}
  </div>
);

const mapPropsToState = state => ({
  isOnModal: state.modal.show,
});

export default connect(
  mapPropsToState,
  null,
)(App);

nav.js

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link, withRouter } from 'react-router-dom';

/* --- Components --- */
import {
  isLoggedIn,
  getCompanyName,
  clearLocalStorage,
} from '../../localStorage';

class Nav extends Component {

  handleUserLogout = async ev => {
    ev.preventDefault();
    await clearLocalStorage();
    return this.props.history.push('/login');
  };

  render() {
    return (
      <div className="nav">  
          {isLoggedIn() ? (
            <div className="flex">
              <p className="mr3 mt2">
                안녕하세요. <span className="b">{getCompanyName()}</span>
                &#8201;님,
              </p>
              <button
                type="button"
                className="login-btn td-none c-text br f-mini"
                onClick={this.handleUserLogout}
              >
                로그아웃
              </button>
            </div>
          ) : (
            <Link className="login-btn td-none c-text br f-mini" to="/login">
              로그인
            </Link>
          )}
      </div>
    );
  }
}

export default withRouter(Nav);

localStorage.js

export const getToken = () => localStorage.token;
export const getCompanyName = () => localStorage.companyName;
export const isLoggedIn = () => !!localStorage.token;
export const saveUserNameAndToken = userData => {
  localStorage.setItem('token', userData.token);
  localStorage.setItem('companyName', userData.companyName);
};
export const clearLocalStorage = () => localStorage.clear();

index.js

import React from 'react';
import { render } from 'react-dom';
import { Provider } from 'react-redux';
import { ConnectedRouter as Router } from 'connected-react-router';
import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
import configureStore, { history } from './store';
import Routes from './routes';

const theme = createMuiTheme({
 ...
});

const store = configureStore();
const root = document.createElement('div');
document.body.appendChild(root);

render(
  <Provider store={store}>
    <MuiThemeProvider theme={theme}>
      <Router history={history}>
        <Routes />
      </Router>
    </MuiThemeProvider>
  </Provider>,
  root,
);

routes.js

import React from 'react';
import { Switch, Route } from 'react-router-dom';
import { hot, setConfig } from 'react-hot-loader';
import { withRouter } from 'react-router';
/* --- Components --- */
import App from './app';
import Loader from './src/shared/loader';

const Home = Loader({
  loader: () =>
    import('./src/components/home/homeContainer' /* webpackChunkName: 'Home' */),
});

...

const routes = props => (
  <div>
    <App history={props.history} />
    <Switch>
      <Route exact path="/" component={Home} />
      <Route path="/login" render={props => <Login {...props} />} />
      <Route path="/users/account" component={Users} />
      <Route component={NoMatch} />
    </Switch>
  </div>
);

const Routes =
  !module.hot || process.env.NODE_ENV === 'production'
    ? routes
    : hot(module)(routes);

export default withRouter(Routes);

I am expecting Nav component to be re-rendered when router location changes.

react: ^16.8.6
react-dom: ^16.8.6
react-loadable: ^5.5.0
react-redux: ^6.0.0
react-router-dom: ^4.3.1
redux: ^4.0.0
connected-react-router: ^6.4.0

Solution

  • I fixed it by saving 'isLoggedIn' state in redux store. And to persist the state from redux store on page refresh, I used 'redux-persist'.

    Now Nav component is re-rendering on router location change and the userName data is saved even on page refresh.

    authReducer.js

    const initialState = {
      isLoggedIn: false,
      userName: '',
    };
    const auth = (state = initialState, action) => {
      switch (action.type) {
        case types.USER_LOGIN:
          return {
            ...state,
            isLoggedIn: true,
            userName: action.payload,
          };
        case types.USER_LOGOUT:
          return {
            ...state,
            isLoggedIn: false,
            userName: '',
          };
        default:
          return state;
      }
    };
    
    

    nav.js

    ...
    {this.props.isLoggedIn ? (
               ...
              ) : (
               ...
              )}
    ...
    
    const mapStateToProps = state => ({
      isLoggedIn: state.auth.isLoggedIn,
      userName: state.auth.userName,
    });
    
    const mapDispatchToProps = dispatch => ({
      userLogout: () => dispatch(userLogout()),
    });
    
    export default withRouter(
      connect(
        mapStateToProps,
        mapDispatchToProps,
      )(Nav),
    );
    

    [ redux-persist SetUp ]

    store.js

    import { applyMiddleware, createStore } from 'redux';
    import thunkMiddleware from 'redux-thunk';
    import { createLogger } from 'redux-logger';
    import { composeWithDevTools } from 'redux-devtools-extension';
    import { routerMiddleware } from 'connected-react-router';
    import createBrowserHistory from 'history/createBrowserHistory';
    import { persistStore, persistReducer } from 'redux-persist';
    import storage from 'redux-persist/lib/storage';
    import createRootReducer from './src/reducers';
    
    export const history = createBrowserHistory();
    
    const persistConfig = {
      key: 'root',
      storage,
    };
    const persistedReducer = persistReducer(
      persistConfig,
      createRootReducer(history), 
    );
    
    const store = createStore(
      persistedReducer,
      composeWithDevTools(
        applyMiddleware(
          routerMiddleware(history), 
          thunkMiddleware,
          createLogger({
            predicate: () => process.env.NODE_ENV === 'development',
            collapsed: true,
          }),
        ),
      ),
    );
    
    export const persistor = persistStore(store);
    export default store;
    
    

    index.js

    import React from 'react';
    import { render } from 'react-dom';
    import { Provider } from 'react-redux';
    import { ConnectedRouter as Router } from 'connected-react-router';
    import { MuiThemeProvider, createMuiTheme } from '@material-ui/core/styles';
    import { PersistGate } from 'redux-persist/lib/integration/react';
    import store, { history, persistor } from './store';
    import Routes from './routes';
    import Loading from './src/shared/loading';
    
    const theme = createMuiTheme({
      palette: {
        primary: { main: '#AE9A65' },
        secondary: { main: '#ee9105' },
        success: { main: '#43A047' },
        warning: { main: '#FFA000' },
        error: { main: '#ed4337' },
        info: { main: '#2196F3' },
      },
    });
    
    const root = document.createElement('div');
    document.body.appendChild(root);
    
    render(
      <Provider store={store}>
        <PersistGate loading={<Loading />} persistor={persistor}>
          <MuiThemeProvider theme={theme}>
            <Router history={history}>
              <Routes />
            </Router>
          </MuiThemeProvider>
        </PersistGate>
      </Provider>,
      root,
    );