3

New to this.

I have a React-Redux app where I have loaded a JWT token and as a result the app indicates this with an Authorized setting = to "true" in state. That works and it now knows its authorised.

On the Navbar I have a "Log Out" option that I want to click and accordingly removes the JWT from local storage and re-renders the navbar to display "Login".

First of all here is the Navbar with the test for Authorize etc:

import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import { Navbar, Nav, NavItem, NavDropdown, MenuItem } from "react-bootstrap"
import { LinkContainer } from 'react-router-bootstrap'

class NavMenu extends Component {
  render() {
    const { isAuthorised, username } = this.props;

    return (
      <div className='main-nav'>
        <Navbar inverse collapseOnSelect>
          <Navbar.Header>
            <Navbar.Brand>
              <Link to={'/'}>JobsLedger</Link>
            </Navbar.Brand>
            {<Navbar.Toggle />}
          </Navbar.Header>
          <Navbar.Collapse>
            <Nav>
              <LinkContainer to={'/'}>
                <NavItem eventKey={1}><span className='glyphicon glyphicon-home'></span> Home</NavItem>
              </LinkContainer>
              <LinkContainer to={'/counter'}>
                <NavItem eventKey={2} ><span className='glyphicon glyphicon-education'></span> Counter</NavItem>
              </LinkContainer>
              <LinkContainer to={'/fetchdata'}>
                <NavItem eventKey={3}><span className='glyphicon glyphicon-th-list'></span> Fetch data</NavItem>
              </LinkContainer>
              {isAuthorised && (
                <NavDropdown eventKey={4} title="Clients" id="basic-nav-dropdown">
                  <LinkContainer to={'/clients/index'}>
                    <MenuItem eventKey={4.1}><span className='glyphicon glyphicon-th-list'></span> Client List</MenuItem>
                  </LinkContainer>
                  <LinkContainer to={'/clients/create'}>
                    <MenuItem eventKey={4.2}><span className='glyphicon glyphicon-user'></span> New Client</MenuItem>
                  </LinkContainer>
                  <MenuItem eventKey={4.3}>Something else here</MenuItem>
                  <MenuItem divider />
                  <MenuItem eventKey={4.4}>Separated link</MenuItem>
                </NavDropdown>
              )}
            </Nav>        
              {isAuthorised ? (
                <Nav pullRight>
                <NavItem eventKey={5}><span className='glyphicon glyphicon-user'></span> Hello {username}</NavItem>
                <LinkContainer to={'/logOut'}>
                  <NavItem eventKey={6}> Log Out</NavItem>
                </LinkContainer>
                </Nav>
              ) : (
                <Nav pullRight>
                  <LinkContainer to={'/login'}>
                    <NavItem eventKey={7} ><span className='glyphicon glyphicon-user'></span> Login</NavItem>
                  </LinkContainer>
                  </Nav>
                )}           
          </Navbar.Collapse>
        </Navbar>
      </div>
    );
  }
}

const mapStateToProps = (state) => ({
  isAuthorised: state.login.isAuthorised,
  username: state.login.username,
})
export default connect(mapStateToProps)(NavMenu);

Note that I have simply included an "isAuthorized" ternary to display different links if Authorized. Upon clicking "Logout" It should recheck that ternary and update...

The other parts of logout

I have a function called "clearJWT" in another file that is imported into the reducer file.

export const clearJwt = () => {
  localStorage.removeItem(TOKEN_KEY)
}

The thing is there is no "event" that I can hang an action off being that it should just render "Logged Out..." and remove the JWT from local storage using the function above.

I have, for the moment combined the actions and the reducer but will separate a little later.

So I have three files then. The reducer with the actions at the top. The Logout container => "logoutContainer" and Logout which is a really simple one liner.

Here is the code I set up.

The action in the reducer file (called reducer.js):

export const logoutUser = () =>
  (dispatch, getState) => {
    clearJwt()
      ({ type: LOGOUT_USER })
  }

The reducer option for LOGOUT_USER:

    case LOGOUT_USER:
    return {
      ...state,
      isAuthorised: false,
    }

and here is my logoutContainer:

import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import Logout from './logout'
import { logoutUser } from './reducer'


class LogoutContainer extends Component {
  static contextTypes = {
    router: PropTypes.object.isRequired
  }

  componentWillMount() {
    console.log('Logout - componentDidMount')
    logoutUser()
  }

  render() {
    return (
      <Logout />
    )
  }
}

const mapStateToProps = (state) => ({
  isAuthorised: state.login.isAuthorised,
})
const mapDispatchToProps = (dispatch) => ({
  logoutUser: () => dispatch(logoutUser()),
})

export default connect( mapDispatchToProps)(LogoutContainer)

I thought I could just call the action on the componentWillMount() which would call "logoutUser()" which would update state while also rendering "Logged out.." and updating the menu bar.

I found this question which indicated I should use "componentDidMount()" however it didnt change anything and logout still isnt called...

I dont think this approach is correct.. (well it doesnt work so thats a clue)

How do I click on the "Log Out" link on the NavBar, go to "LogoutContainer", automatically call the action ( logoutUser() ) and re-render the page as well as have the Navbar recheck the state re "Authorised" and realise now that its not authorized and rerender the navbar also?

Is this the right way to do it even?

In mycase "logoutUser()" isnt even called..

Community
  • 1
  • 1
si2030
  • 3,895
  • 8
  • 38
  • 87

1 Answers1

3

I need to post this as an answer because its to long for a comment but first treat it as a suggestion and some code cleanup. You have to test it yourself.

First your action. This is a "thunk action" handler way of writing an action. You can see that it a function returning another function (hence thunk). In your code you never actually invoked it (this is why nothing gets called, it stops at the thunk. If your want to use the redux-thunk middleware read up on how to use it, its a little bit different than "normal" dispatching. Redux-thunk docs.

Secondly your not calling the dispatcher so you action wont get called and redux wont re-render your app.

export const logoutUser = () =>
  (dispatch, getState) => {
    clearJwt()
    dispatch({ type: LOGOUT_USER }) //Call the dispatcher with your action
  }

In your container you need to use the thunks and actions correctly as mentioned by @si2030 in the comments.

import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import Logout from './logout'
import { logoutUser } from './reducer'


class LogoutContainer extends Component {
  componentDidMount() {
    this.props.logoutUser();
  }

  render() {
    return (
      <Logout />
    )
  }
}

const mapStateToProps = (state) => ({
  isAuthorised: state.login.isAuthorised,
});
//Just return a map with your actions and call them with this.props.yourAction
  const mapDispatchToProps = {
    logoutUser
  };

};
//Connect is a function that takes your state map and your actions map.
export default connect(mapStateToProps,mapDispatchToProps)(LogoutContainer) 

I also changed the lifecycle to didMount. You need to set state, re-render etc. (redux will do this for you though). WillMount makes no sence here. React docs explains it.

Please note again, this setup uses the redux-thunk middleware. Make sure you have it initialized in your store. Redux-thunk docs.

Per Svensson
  • 751
  • 6
  • 14
  • Its only just now that I have realised that you have two types of props, ones from state which is where the "mapStateToProps" is used and what I didnt understand but another set of props or prop (for the most part) that is, in my case a function. Its another prop created in the action. Use this to fire the action and it has nothing to do with the state props... I got this just half an hour ago and it wasnt obvious to me from what I had read to date. Once you put "this.props" infront of the logoutUser() function it works. Thank you for point this out.. it makes alot more sense now. – si2030 May 02 '17 at 13:59