0

I am busy with a little proof of concept where basically the requirement is to have the home page be a login screen when a user has not logged in yet, after which a component with the relevant content is shown instead when the state changes upon successful authentication.

I have to state upfront that I am very new to react and redux and am busy working through a tutorial to get my skills up. However, this tutorial is a bit basic in the sense that it doesn't deal with connecting with a server to get stuff done on it.

My first problem was to get props to be available in the context of the last then of a fetch as I was getting an error that this.props.dispatch was undefined. I used the old javascript trick around that and if I put a console.log in the final then, I can see it is no longer undefined and actually a function as expected.

The problem for me now is that nothing happens when dispatch is called. However, if I manually refresh the page it will display the AuthenticatedPartialPage component as expected because the localstorage got populated.

My understanding is that on dispatch being called, the conditional statement will be reavaluated and AuthenticatedPartialPage should display.

It feels like something is missing, that the dispatch isn't communicating the change back to the parent component and thus nothing happens. Is this correct, and if so, how would I go about wiring up that piece of code?

The HomePage HOC:

import React from 'react';
import { createStore, combineReducers } from 'redux';
import { connect } from 'react-redux';
import AuthenticatedPartialPage from './partials/home-page/authenticated';
import AnonymousPartialPage from './partials/home-page/anonymous';
import { loggedIntoApi, logOutOfApi } from '../actions/authentication';
import authReducer from '../reducers/authentication'

// unconnected stateless react component
const HomePage = (props) => (
    <div>
        { !props.auth 
            ? <AnonymousPartialPage /> 
            : <AuthenticatedPartialPage /> }
    </div>
);

const mapStateToProps = (state) => {
    const store = createStore(
        combineReducers({
            auth: authReducer
        })
    );

    //  When the user logs in, in the Anonymous component, the local storage is set with the response
    //  of the API when the log in attempt was successful.
    const storageAuth = JSON.parse(localStorage.getItem('auth'));
    if(storageAuth !== null) {

        //  Clear auth state in case local storage has been cleaned and thus the user should not be logged in.
        store.dispatch(logOutOfApi());

        //  Make sure the auth info in local storage is contained in the state.auth object.
        store.dispatch(loggedIntoApi(...storageAuth))
    }

    return {
        auth: state.auth && state.auth.jwt && storageAuth === null 
            ? state.auth 
            : storageAuth
    };
}

export default connect(mapStateToProps)(HomePage);

with the Anonymous LOC being:

import React from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import { loggedIntoApi } from '../../../actions/authentication';

export class AnonymousPartialPage extends React.Component {
    constructor(props) {
        super(props);
    }
    onSubmit = (e) => {
        e.preventDefault();

        const loginData = { ... };

        //  This is where I thought the problem initially occurred as I 
        //  would get an error that `this.props` was undefined in the final 
        //  then` of the `fetch`. After doing this, however, the error went
        //  away and I can see that `props.dispatch is no longer undefined 

        //  when using it. Now though, nothing happens.
        const props = this.props;

        fetch('https://.../api/auth/login', {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
            },
            body: JSON.stringify(loginData)
        })
        .then(function(response) {
            return response.json();    
        })
        .then(function(data) {
            if(data && data.jwt) {     
                props.dispatch(loggedIntoApi(data));
                localStorage.setItem('auth', JSON.stringify(data));
            }
            //  else show an error on screen 
        });
    };
    render() {
      return (
        <div>
             ... onSubmit gets called successfully somewhere in here ...
        </div>
      );
    }
}

export default connect()(AnonymousPartialPage);

the action:

// LOGGED_INTO_API
export const loggedIntoApi = (auth_token) => ({
    type: 'LOGGED_INTO_API',
    auth: auth_token
});

// LOGGED_OUT_OF_API
export const logOutOfApi = (j) => ({
    type: 'LOG_OUT_OF_API'
});

and finally the reducer:

const authDefaultState = { };

export default (state = authDefaultState, action) => {
  switch (action.type) {
    case 'LOGGED_INTO_API':
      // SOLUTION : changed this line "return action.auth;" to this:
      return { ...action.auth, time_stamp: new Date().getTime() }
    case 'LOG_OUT_OF_API':
      return { auth: authDefaultState  };
    default:
      return state;
  }
};
Ebbs
  • 1,030
  • 3
  • 20
  • 38

1 Answers1

1

My suggestion would be to make sure that the state that you are changing inside Redux is changing according to javascript's equality operator!. There is a really good answer to another question posted that captures this idea here. Basically, you can't mutate an old object and send it back to Redux and hope it will re-render because the equality check with old object will return TRUE and thus Redux thinks that nothing changed! I had to solve this issue by creating an entirely new object with the updated values and sending it through dispatch().

Essentially:

x = {
  foo:bar
}

x.foo = "baz"

dispatch(thereWasAChange(x)) // doesn't update because the x_old === x returns TRUE!

Instead I created a new object:

x = {
  foo:"bar"
}

y = JSON.parse(JSON.stringify(x)) // creates an entirely new object

dispatch(thereWasAChange(y)) // now it should update x correctly and trigger a rerender

// BE CAREFUL OF THE FOLLOWING!

y = x

dispatch(thereWasAChange(y)) // This WON'T work!!, both y and x reference the SAME OBJECT! and therefore will not trigger a rerender

Hope this helps!

  • I am marking this as the answer because you pointed me in the right direction, thanks. The problem was that the auth token still seemed the same at the reducer level (to redux I guess, even though it went from an empty object to something distinctly not empty.) To fix the problem, I added a timestamp to the auth object which will change every time the object is updated. I will make the the correction in my code above and leave the "broken" code in comments. – Ebbs Sep 09 '18 at 18:50
  • Glad I could help :) – Robin Kurosawa Sep 10 '18 at 15:06