user3241846
user3241846

Reputation: 677

getting Redux state error in a page im redirecting out of

Background

I have an events page that uses JWT for authentication. I have a signout function in the header across the whole application. The basic premise, its a event display app with a many to many between users and events. Using Mongo if that matters.

I click on sign out on all pages and it works just fine. However on Landing component it throws this error.

    TypeError: Cannot read property 'id' of null
    Function.stateToProps [as mapToProps]
    Path/client/src/components/EventActionButton.js:69
      66 | 
      67 | const stateToProps = (state) => {   
      68 |     return {
    > 69 |         userId: state.user.id
      70 |     }
      71 | }
      72 | 
    View compiled
    ▼ 19 stack frames were expanded.
    mapToPropsProxy
   Path/client/node_modules/react-redux/es/connect/wrapMapToProps.js:41
    handleNewPropsAndNewState
    Path/client/node_modules/react-redux/es/connect/selectorFactory.js:30
    handleSubsequentCalls
    Path/client/node_modules/react-redux/es/connect/selectorFactory.js:56
    pureFinalPropsSelector
    Path/client/node_modules/react-redux/es/connect/selectorFactory.js:63
    runComponentSelector [as run]
    Path/client/node_modules/react-redux/es/components/connectAdvanced.js:21
    Connect.componentWillReceiveProps
    Path/client/node_modules/react-redux/es/components/connectAdvanced.js:152
    callComponentWillReceiveProps
    Path/client/node_modules/react-dom/cjs/react-dom.development.js:12399

Header.js (where signout btn is)

import React , {Component} from 'react';
import {connect} from 'react-redux';
import {startSignOut} from '../actions/auth';

const Header = class Header extends Component {

    handleSignOut = () => {
        this.props.startSignOut();
    }

    render () {
        return (
            <div>
                <span>circle</span>
                <span>user</span>
                <button onClick={() => this.handleSignOut()}>Sign out</button>
            </div>
        )  
    }

}

const mapDispatchToProps = (dispatch) => ({
    startSignOut: () => startSignOut(dispatch)
});

export default connect(undefined, mapDispatchToProps)(Header);

Landing.js (Landing page at / URI)

import RequireAuth from './RequireAuth';
import {fetchEvents} from '../actions/event';
import {connect} from 'react-redux';
import EventItem from './EventItem';
import Header from './Header';

const EventDisplay = class EventDisplay extends Component {

    componentDidMount = () => {
        this.props.fetchEvents();
    }

    handleAddEvent = () => {
        this.props.history.push('/addevent');
    }

    handleSignOut = () => {
        this.props.startSignOut();
    }

    render() {
        return (
            <div>
                <Header signOut={this.handleSignOut}/>
                {
                    this.props.events.map((event, ind) => {
                        return <EventItem key={ind} history={this.props.history} index={ind + 1} event={event}/>
                    })
                }
                <button onClick={() => this.handleAddEvent()}>+</button>
            </div>
        )
    }
}

const mapDispatchToProps = (dispatch) => ({
    fetchEvents: (userId) => dispatch(fetchEvents(userId))
});

const mapStateToProps = (state) => ({
    events: state.events
})

const connectedAuth = RequireAuth(connect(mapStateToProps, mapDispatchToProps)(EventDisplay));

export default connectedAuth;

EventItem.js (reusable component for each event)

import EventActionButton from './EventActionButton';
import RequireAuth from './RequireAuth';

const EventItem = (props) => {

    const event = props.event;
    const handleDetailView = () => {
        props.history.push({
            pathname: "/eventview",
            state: { event: props.event }
          });
    }
    return (
        <div>
            <div onClick={() => handleDetailView()}>
                <span >{event.title}</span>
                <span>{event.description}</span>
                <span>{event.host.first_name + " " + event.host.last_name}</span>
                <span>{event.date}</span>
                <span>{event.capacity}</span>
            </div>
            <EventActionButton displayEvent={event}/>
        </div>

    )
}

export default RequireAuth(EventItem);

EventActionButton.js (Button in EventItem allowing users to attend or leave events)

import {connect} from 'react-redux';
import {editEventAssociation} from '../actions/event';
import RequireAuth from './RequireAuth';

const EventActionButton = class EventActionButton extends Component {
    constructor(props) {
        super(props);

        this.state ={
            edit: false,
            joined: false
        }
    }

    onClick = (e) => {        
        console.log(this.props);
        var currentUser = this.props.userId;
        if(this.state.edit) {
            this.props.history.push('/addevent');
        } else {
            let userIds = this.props.displayEvent.users.map((user) => {
                return user._id;
            });
            console.log(userIds);
            if (this.state.joined) {
                const modifiedUsers = userIds.filter((id) => id === this.props.userId);
                userIds = modifiedUsers;
                console.log(userIds);
            } else {
                userIds.push(this.props.userId);
                console.log(userIds);
            }
            console.log("Joined " + this.state.joined);
            console.log("Edited " + this.state.edit);

            this.props.editEventAssociation(this.props.displayEvent._id, userIds, currentUser, this.state.joined);

        }
    }

    componentWillMount = () => {
        const event = this.props.displayEvent;
        if (event.host._id === this.props.userId)  {
            this.setState({ edit: true, joined: false });
        } else {
            event.users.map((user) => {
                if (user._id === this.props.userId) {
                   return this.setState({joined: true, edit: false});
                } else {
                   return this.setState({join: false, edit: false});
                }
            });
        }

    }
    render() {
        const text = this.state.edit ? "Edit" : this.state.joined ? "Leave" : "Join";
        return (
            <>   
                <button value={text} onClick={this.onClick}>{text}</button>
            </>
        )
    }
}

const stateToProps = (state) => {   
    return {
        userId: state.user.id
    }
}

const mapDispatchToProps = (dispatch) => {
    return ({
        editEventAssociation: (eventId, users, currentUser, joinedEvent) => dispatch(editEventAssociation(eventId, users, currentUser, joinedEvent))
    })
}
const connectedRouterButton = connect(stateToProps, mapDispatchToProps)(EventActionButton);
export default RequireAuth(connectedRouterButton);

and just in case, this is the signout action.

export const startSignOut = (dispatch) => {

    localStorage.removeItem('token');    
    localStorage.removeItem('user');
    dispatch(removeUser());
    dispatch(signOut());
}

where remove user removes currentUser in redux store and signOut removes auth token from redux store. Somehow Action button rerenders even though sign out redirects to /signin page using an HOC RequireAuth.

I dont understand how EventActionButton's re-render is triggered? Im not redirecting to Landing on /, Im redirecting to /signin

Upvotes: 0

Views: 93

Answers (1)

Sam Chahine
Sam Chahine

Reputation: 630

This makes sense because you're logging out so there's technically no "user" in "state.user", so instead of trying to access "state.user.id", assign a variable to "state.user" and in the constructor or wherever you're using the "id", do a check for if "state.user" is NULL and then assign the ID.

Upvotes: 0

Related Questions