Reputation: 461
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.
Upvotes: 2
Views: 43
Reputation: 888
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:[] } }
Upvotes: 1
Reputation: 33984
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} />)}
Upvotes: 1
Reputation: 11267
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.
Upvotes: 1