Reducer Action Not Dynamically Updating

mapdispatchtoprops
redux
redux-thunk
redux update array of objects
redux reducer not updating state
redux dispatch action from reducer
redux update nested array
update component state from reducer

I'm creating a basic CRUD app using React/Redux with a Rails API, and when I submit a car on my car-form, I get an error message - but refreshing the browser shows the car.

The error says Uncaught TypeError: Cannot read property 'map' of undefined on line 20 of my Cars.js file:

    import React, { Component } from 'react';
import { connect } from 'react-redux';

import CarCard from '../components/CarCard';
import CarForm from './CarForm';
import './Cars.css';
import { getCars } from '../actions/cars';


class Cars extends Component {

componentDidMount() {
    this.props.getCars()
}

render() {
    return (
    <div className="CarsContainer">
        <h3>Cars Component</h3> 
        {this.props.cars.cars.map(car => <CarCard key={car.id} car={car} />)}
        <CarForm />
    </div>
    );
}

}

const mapStateToProps = (state) => {
    return ({
        cars: state.cars
    })
}

export default connect(mapStateToProps, { getCars })(Cars);

Here's my createCar action creator:

const addCar = car => {
return {
    type: 'CREATE_CAR_SUCCESS',
    car
}}

And my createCar async action:

export const createCar = car => {
return dispatch => {
    return fetch(`${API_URL}/cars`, {
        method: "POST",
        headers: {
            'Content-type': 'application/json'
        },
        body: JSON.stringify({ car: car })
    })
    .then(response => {
      try {
        return response.json()
      } catch(error) {
        console.log(error);
      }
    })
    .then(cars => {
        dispatch(addCar([car]));
        dispatch(resetCarForm())
    })
    .catch(error => console.log(error + 'createCar POST failed'))
}}

I'm not sure what's going wrong here, seeing as the app reflects my changes after I reload. Ultimately I'm trying to show that information without having to refresh the page.

The problem is that when your component mounts, it doesn’t have the cars array and instead it has an undefined value. This happens because getCars() is asynchronous.

Solution 1: add a defaultProp to the component:

Component.defaultProps = {
cars: { cars: [] }
}

Solution 2: Add a cars key to the reducer’s initialState

initialState: { cars:{ cars:[] } }

How to update nested props in reducer? � Issue #432 � reduxjs/redux , I can't figure out the correct way to update nested property in reducers state. When I write return in such was the "data" property is not merged. data: { state .data, /* how do I dynamically set the key here to change key2 based on the action? If you use combineReducers(), each of the reducer functions is executed for each dispatched action. They then decide purely based on the action type whether the action should affect its slice of the state. If so, an updated copy of this slice is returned. If its slice is not affected by the action, it is returned unchanged.

You are rendering before your async action puts the values in the state. Try returning null from render if your state is not set yet:

render() {
    if(!this.props.cars.cars){
      return null;
    }
    return (
    <div className="CarsContainer">
        <h3>Cars Component</h3> 
        {this.props.cars.cars.map(car => <CarCard key={car.id} car={car} />)}
        <CarForm />
    </div>
    );
}

In other words, if your state does not have a list of things to render return null - I think the above if will work, but you might want to console.log("Cars in render", this.props.cars) to see what you are getting.

The better option, IMO, is to set your initial state so that this.props.cars is [] and then you don't have to return null and have a special case in your render method. I would need to see your reducer to suggest how to do that, but if you make it have a sensible default/initial state you should be able to easily do this.

Redux: Updating the State with the Fetched Data from , The all IDs reducer is only interested in the actions with the all filter. I'm removing the existing cases because the data does not live locally anymore. IDs corresponding to all filter, I can read state IDs by filter and pass all as a dynamic key. And like other reducers, combineReducers() does not create a new object if all of the reducers provided to it do not change state. # Note for ES6 Savvy Users Because combineReducers expects an object, we can put all top-level reducers into a separate file, export each reducer function, and use import * as reducers to get them as an object with

You are doing action call getCars in componentDidMount and this lifecycle method gets called after first render so on Initial render this.props.cars will be undefined

If you are getting this.props.cars like

   {
       "cars": [....]
    }

Then you need to do conditional check before accessing cars object

Change

       {this.props.cars.cars.map(car => <CarCard key={car.id} car={car} />)}

To

         {this.props.cars && this.props.cars.cars.map(car => <CarCard key={car.id} car={car} />)}

Code Splitting, To code split with Redux, we want to be able to dynamically add reducers to the it will swap the internal reducer function reference, and dispatch an action to� A reducer is a function that determines changes to an application’s state. It uses the action it receives to determine this change. We have tools, like Redux, that help manage an application’s state changes in a single store so that they behave consistently.

Reducing Boilerplate, It's important that actions being objects you have to dispatch is not boilerplate, It is a common convention that actions have a constant type that helps reducers ( or With Redux, the same update logic can be described as a reducing function: . Note that action has a default value of {} So action.type will be undefined if we forget to pass an action; Use Object.assign to merge current state with action’s payload Does the right thing the first time because initialState is the default value of state; Reducer functions always return a new state object

Actions and reducers: updating state � Human Redux, You may not return undefined . If you change state at all, replace it (more on this later). Every reducer processes every action even if it doesn't do� The combineReducers utility included with Redux is very useful, but is deliberately limited to handle a single common use case: updating a state tree that is a plain Javascript object, by delegating the work of updating each slice of state to a specific slice reducer. It does not handle other use cases, such as a state tree made up of Immutable

Managing the URL in a Redux app, This means you can update the URL in response to your own app's actions as you Note, doing this is not quite the official API. react-router-redux creates and the URL but the cleanest, most redux way, is to update state in your reducers. Every time the store calls the reducer, it passes in the last-known state. Around and around it goes: Redux sitting there waiting for an action, handling that action, updating the state, re-rendering your app, on and on forever. So that’s it! That’s how Redux reducers work, in a nutshell. Not too bad?

Comments
  • What is the output of cars if you do console.log(this.props.cars)
  • post the reducer code and the resetCarForm function
  • Is adding an initialState different than the export default (state = {cars: []}, action)... that I currently have?
  • Yes. Its only different because {cars: []} isn’t the same as {cars: {cars: []}}. I guess your problem is that you are getting your array nested in your redux store. Thats why you are doing this.props.cars.cars.map(...) at Cars component. To verify this try a console.log at the response in your Promise’s .the
  • Thanks, I apologize but where specifically would I be adding that code?
  • If you go for Solution 1 -> do it at Cars component, so before exporting it you write: Cars.defaultProps ={...}.If you go for Solution 2 you can do it at your reducer: export default (state={cars: {cars:[]}},action)
  • console.logging it returns an empty cars array, so I need to find a way to get the cars array before this all gets loaded
  • I swapped out the line, but cars is still returning as undefined. Do I need to restart my server or anything?
  • @victor.ja I added that, and this time it submits, and only renders the form again (with the same information). Submit > adds to database but doesn't show any cars > keeps the info in the form. Upon refresh I see the submitted car
  • componentDidMount only triggers once, when the component is mounted. You need to call getCars() again at componentDidUpdate() to trigger a re-fetch of cars [getCars]
  • @victor.ja Worked! Thank you so much for your help.