0
votes

The following code works, but does not re-drive the app render() upon a successful redux dispatch call. I cannot see what is wrong. There is mocked up data here I put in to simplify things trying to debug this rudimentary problem.

What works:

  1. The app react component renders the first time
  2. The dispatcher successfully invokes the reducer
  3. The reducer successfully invokes the mapStateToProps() with the correct state change data

What fails:

  1. The app re-render is never invoked upon the successful dispatch/reducer state change

index.js

import { createStore } from 'redux';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import './css/index.css';
import App from './containers/App';
import reducer from './reducers';


const store = createStore(
  reducer,
  {schooldata: {}}
)

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById('root')
)

store.dispatch({type: "LOAD_SCHOOL_DATA"});

containers/App.js

import React from 'react';
import { connect, Provider } from 'react-redux';
import {
  Route,
  NavLink,
  BrowserRouter as Router,
  Switch,
} from 'react-router-dom';
import Home from "../components/Home";
import Photos from "../components/Photos";
import Attendees from "../components/Attendees";

class App extends React.Component {

  constructor(props) {
    super(props);
  }

  componentDidMount() {
    
  };

  render() {
    console.log("App is rendering...");
    console.log("Props in app: %s", JSON.stringify(this.props));
    const src=`${this.props.images}/pic.jpg`;
    console.log("src: %s", src);

    return (
      <div>
        <Router>
      <div>
        <ul>
          <li>
            <NavLink exact activeClassName="active" to="/">
              Home
            </NavLink>
          </li>
          <li>
            <NavLink activeClassName="active" to="/photos">
              Photos
            </NavLink>
          </li>
          <li>
            <NavLink activeClassName="active" to="/attendees">
              Attendees
            </NavLink>
          </li>
        </ul>
        <hr />
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/photos" component={Photos} />
          <Route path="/attendees" component={Attendees} />
        </Switch>
      </div>
    </Router>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  console.log("Mapping this: %s", JSON.stringify(state));
  return (state.schooldata);
}


const mapDispatchToProps = function (dispatch) {
  console.log("Mapping dispatch to props");
  return({});
}


export default connect(mapStateToProps)(App);

reducers/index.js

import axios from 'axios';
import { combineReducers } from 'redux';
import { LOAD_SCHOOL_DATA, ADD_PHOTO, ADD_ATTENDEE } from '../constants/ActionTypes';

const routeAction = (state, action) => {
    console.log("routeAction: %s", JSON.stringify(action));
    switch(action.type) {
        case LOAD_SCHOOL_DATA:
            console.log("Action type match.");
            return ({
                schooldata: {images:"blah", photos:"bleep"},
              });
        case ADD_PHOTO:
            return addPhoto(state);
        case ADD_ATTENDEE:
            return addAttendee(state);
        default:
            return {};
    }
}


const loadSchoolData = async () => {
    await axios.get("/schooldata").then((response) => {
        console.log("School data from server: %s", JSON.stringify(response.data));
        return(response.data);
    })
}

const addPhoto = (state) => {
    return state.loadSchoolData.photoList
}

const addAttendee = (state) => {

}

export default combineReducers({
    routeAction
});

Browser debug output

routeAction: {"type":"@@redux/INITx.t.b.e.s"} index.js:6:12
routeAction: {"type":"@@redux/PROBE_UNKNOWN_ACTION0.c.1.k.j.c"} index.js:6:12
routeAction: {"type":"@@redux/INITx.t.b.e.s"} index.js:6:12
Mapping this: {"routeAction":{}} App.js:64:10
App is rendering... App.js:24:12
Props in app: {} App.js:25:12
src: undefined/pic.jpg App.js:27:12
props: {"history":{"length":2,"action":"POP","location":{"pathname":"/","search":"","hash":""}},"location":{"pathname":"/","search":"","hash":""},"match":{"path":"/","url":"/","isExact":true,"params":{}}} Home.js:15:12
routeAction: {"type":"LOAD_SCHOOL_DATA"} index.js:6:12
Action type match. index.js:9:20
Mapping this: {"routeAction":{"schooldata":{"images":"blah","photos":"bleep"}}} App.js:64:10
routeAction: {"type":"LOAD_SCHOOL_DATA"} index.js:6:12
Action type match. index.js:9:20
Mapping this: {"routeAction":{"schooldata":{"images":"blah","photos":"bleep"}}} App.js:64:10
routeAction: {"type":"LOAD_SCHOOL_DATA"} index.js:6:12
Action type match. index.js:9:20
Mapping this: {"routeAction":{"schooldata":{"images":"blah","photos":"bleep"}}} App.js:64:10
1
return (state.schooldata) - based on your console output, this is undefined, I think you want return (state.routeAction.schooldata) - Robin Zigmond
(Note that this routeAction property holds your whole state because you are using combineReducers with just one reducer, called routeAction) - Robin Zigmond
Thank you, but it is changing state, the change is successful as evinced by the invocation of mapStateToProps, and, thus, App.render is supposed to be called again. It is my understanding that is the point of having redux manage state: any change will re-render all components the store is connected with. - Ray Walker
Also, I tried changing the reducer to change state return (state.routeAction.schooldata), as suggested by Robin Zigmond, but that did not fix it. That output follows Note: no re-render upon state change. ``` routeAction: {"type":"LOAD_SCHOOL_DATA"} index.js:6:12 Action type match. index.js:9:20 Mapping this: {"routeAction":{"routeActions":{"schooldata":{"images":"blah","photos":"bleep"}}}} ``` - Ray Walker
Yes, the store is updating, but your mapStateToProps doesn't pick up the change (because it always returns undefined). Connecting your component to Redux doesn't change the fundamentals that the component will only rerender if it receives new props (or state). The sequence is: store updates -> mapStateToProps is executed (as you saw) -> it's return value is merged into the component's actual props -> React checks for changes from the last render, and if so, rerenders. In your case there are no changes because of the incorrect mapStateToProps - Robin Zigmond

1 Answers

0
votes

Found the problem. mapStateToProps(), in App.js, was not returning a valid key in the state JSON structure, thus, the component's props were not changing. This means it's not really the state change that drives a re-rendering of the component, it's its props change that does. This is what fixed this little mock up:

App.js

const mapStateToProps = (state, props) => {
  console.log("Mapping this: %s", JSON.stringify(state));
  return (state.routeAction);  // Return valid key!
}