Reputation: 2671
I am building a simple contacts app using React and Redux. My state object looks like the following:
{
"contacts": [
{
"name": "Miguel Camilo",
"phone": "123456789"
},
{
"name": "Peter",
"phone": "883292300348"
},
{
"name": "Jessica",
"phone": "8743847638473"
},
{
"name": "Michael",
"phone": "0988765553"
}
],
"activeContact": {
"name": "Peter",
"phone": "883292300348"
}
}
I am trying to build an add contacts functionality which will take a name and number from inputs on the page and add them to the contacts
array in the state. This is how it is built:
ContactsList component:
class ContactList extends Component {
renderList() {
return this.props.contacts.map((contact) => {
return (
<li
key={contact.phone}
onClick={() => this.props.selectContact(contact)}
className='list-group-item'>{contact.name}</li>
);
});
}
render() {
return (
<ul className = 'list-group col-sm-4'>
{this.renderList()}
</ul>
);
}
}
function mapStateToProps(state) {
return {
contacts: state.contacts
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ selectContact: selectContact }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ContactList)
AddContactsComponent:
class AddContactModal extends Component {
constructor() {
super();
this.state = {firstName: "", phone: ""};
}
handleNameChange(event) {
this.setState({firstName: event.target.value})
}
handlePhoneChange(event) {
this.setState({phone: event.target.value});
}
render() {
return(
<div>
<input type="text" className="name" placeholder="Contact Name" onChange={(event) => this.handleNameChange(event)}/>
<input type="text" className="phone" placeholder="Contact Phone" onChange={(event) => this.handlePhoneChange(event)}/>
<AddContactButton firstName={this.state.firstName} firstName={this.state.phone} onClick={() => this.props.addContact({"name": this.state.firstName, "phone": this.state.phone})}/>
</div>
);
}
}
function mapStateToProps(state) {
console.log('mapstatetoprops:');
console.log(state);
return {
contacts: state.contacts
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ addContact: addContact }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(AddContactModal)
Contacts Reducer (containing the ADD_CONTACT handler):
export default function (state = { contacts : [] }, action) {
switch (action.type) {
case 'ADD_CONTACT':
console.log(state);
return Object.assign({}, state, {
contacts: state.concat([
{
name: action.payload.name,
phone: action.payload.phone
}
])
})
default:
return state;
}
}
App's index.js where the store is built:
const createStoreWithMiddleware = applyMiddleware()(createStore);
const initialState = {
"contacts": [
{
"name": "Miguel Camilo",
"phone": "123456789"
},
{
"name": "Peter",
"phone": "883292300348"
},
{
"name": "Jessica",
"phone": "8743847638473"
},
{
"name": "Michael",
"phone": "0988765553"
}
],
"activeContact": null
};
ReactDOM.render(
<Provider store={ createStoreWithMiddleware(reducers, initialState) }>
<App />
</Provider>
, document.querySelector('.container'));
And combining my reducers:
const rootReducer = combineReducers({
contacts: ContactsReducer,
activeContact: ActiveContactReducer
});
export default rootReducer;
My issue is that when I click my Add Contact button a new contacts
array gets added within the existing contacts
array in the state, and then for some odd reason my initial contacts list gets added at the top level of the state. So after clicking the add contact button (and before it errors out) when I log this.props.contacts
it looks like the following:
{
"0": {
"name": "Miguel Camilo",
"phone": "123456789"
},
"1": {
"name": "Peter",
"phone": "883292300348"
},
"2": {
"name": "Jessica",
"phone": "8743847638473"
},
"3": {
"name": "Michael",
"phone": "0988765553"
},
"contacts": [
{
"name": "Miguel Camilo",
"phone": "123456789"
},
{
"name": "Peter",
"phone": "883292300348"
},
{
"name": "Jessica",
"phone": "8743847638473"
},
{
"name": "Michael",
"phone": "0988765553"
},
{
"name": "123",
"phone": "123"
}
]
}
So a new contacts
array was added within the original contacts
array containing the original values plus the new one. While the original contacts
array remains unchanged.
I'm not sure why this is. Originally I thought the state
object getting passed to my reducer was the full state, but it seems to be only the contacts
portion of my state, so I modified my Object.assign() call to account for this by changing it from contacts: state.contacts.concat()
to contacts: state.concat()
but this still did not fix the issue.
I want the state that comes back to my ContactsList component to look like:
{
"contacts": [
{
"name": "Miguel Camilo",
"phone": "123456789"
},
{
"name": "Peter",
"phone": "883292300348"
},
{
"name": "Jessica",
"phone": "8743847638473"
},
{
"name": "Michael",
"phone": "0988765553"
},
{
"name": "this is the new contact",
"phone": "123"
}
],
"activeContact": {
"name": "Peter",
"phone": "883292300348"
}
}
Just the original state with one extra contact added to my contacts
array. Any ideas what is wrong with my implementation? Any help is appreciated!
Upvotes: 2
Views: 46
Reputation: 281646
The problem is how you assign your initialState. Since you use combineReducers with one key being contacts, your contactsReducers gets
[
{
"name": "Miguel Camilo",
"phone": "123456789"
},
{
"name": "Peter",
"phone": "883292300348"
},
{
"name": "Jessica",
"phone": "8743847638473"
},
{
"name": "Michael",
"phone": "0988765553"
}
],
whereas you expect it to be
{
contacts: [
{
"name": "Miguel Camilo",
"phone": "123456789"
},
{
"name": "Peter",
"phone": "883292300348"
},
{
"name": "Jessica",
"phone": "8743847638473"
},
{
"name": "Michael",
"phone": "0988765553"
}
]
}
So the change you need to make is
export default function (state = [], action) {
switch (action.type) {
case 'ADD_CONTACT':
console.log(state);
return state.concat([
{
name: action.payload.name,
phone: action.payload.phone
}
])
default:
return state;
}
}
Once you make the above change, things will work
Upvotes: 1
Reputation: 94
you should change your state like this
export default function (state = { contacts : [] }, action) {
switch (action.type) {
case 'ADD_CONTACT':
console.log(state);
return {
...state,
contacts: [...state.contacts, ...contacts]
}
default:
return state;
}
}
Upvotes: 0