intA
intA

Reputation: 2671

Redux state not getting built as expected in reducers

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

Answers (2)

Shubham Khatri
Shubham Khatri

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

Vikas Verma
Vikas Verma

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

Related Questions