1

What is the best way to update an objects state in redux using one function for multiple inputs?

I have 3 inputs - if possible, I'd like to update corresponding input field to redux object field using one function... or best practice.

// Contact.js

this.state = {
    contactInfo: {
        firstName: '',
        address: '',
        city: '',
    }
}


onChangeInfo = (event, action) => {
    const { dispatch } = this.props;
    const { contactInfo } = this.state;

    // Is this an issue?
    contactInfo[event.target.name] = event.target.value;

    if (action === 'CHANGE') {
        dispatch(updateContactInfo(contactInfo));
        this.setState({ contactInfo });
    } else {
        this.setState({ contactInfo });
    }
}

render() {

const { firstName, address, city } = this.state.contactInfo;

return (
<div>
    <div>
        <input placeholder=" " type="text" name='firstName' value={firstName} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
        <div className="placeholder"><span>First &amp; last name</span></div>
    </div>
    <div>
        <input placeholder=" " type="text" name="address" value={address} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
        <div className="placeholder"><span>Address</span></div>
    </div>
    <div>
        <input placeholder=" " type="text" name="city" value={city} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
        <div className="placeholder"><span>City</span></div>
    </div>
</div>
)}

// Reducer


const initialState = {
    contactInformation: [],
}

export default function (state, action) {
    state = state === undefined ? initialState : state;
    switch (action.type) {
        case 'CONTACT_INFO': {
            state.contactInformation.test.info = payload;
            return Object.assign({}, state, action.payload);
        }
        default: return state;
    }
}
fosho
  • 127
  • 8
  • 2
    When you use redux, you are not supposed to change your state directly. It must be done only through reducer functions – edison16029 Jun 10 '20 at 15:20

3 Answers3

1

I would suggest you to maintain three different actions as best practise as an action is always a pure function and specific to one particular operation.

export const firstName = (firstName) => ({
    type : 'FIRST_NAME',
    payload : firstName
});

export const address = (address) => ({
    type : 'ADDRESS',
    payload : address
});

export const city = (city) => ({
    type : 'CITY',
    payload : city
});

And, in the reducer, update the store based on action.type

edison16029
  • 346
  • 1
  • 9
  • I would suggest this answer if there is something meaningful happening when one input is submitted at a time. Otherwise I'd suggest the OP goes with my answer – alaboudi Jun 10 '20 at 15:32
  • 1
    I agree, i just mentioned this as OP asked for best practise. – edison16029 Jun 10 '20 at 15:37
  • This is most likely what I will go with if I can't get Jozefs to work. This is very straightforward but seems to be a lot redundant code. – fosho Jun 10 '20 at 17:11
1

I don't see the point of using setState in this case

this.state = {
    contactInfo: {...this.props}
}

onChangeInfo = ({target: {name, value}}, action) => {
    const { dispatch } = this.props;
    const contactInfo = {[name]: value};

    if (action === 'CHANGE') {
        dispatch(updateContactInfo(contactInfo));
    }
}

Example

const { Component, useState, useEffect } = React;
const { bindActionCreators, combineReducers, createStore, applyMiddleware, compose } = Redux;
const { connect, Provider } = ReactRedux;

const initalState = {
    contactInfo: {
      firstName: '',
      address: '',
      city: ''
  }
}

const reducer = (state = initalState, action) => {
  switch (action.type) {
    case 'CONTACT_INFO': {
      const newState = {
        ...state,
        contactInfo: {
          ...state.contactInfo,
          ...action.payload.contactInfo
        }
      };

      return newState;
    }
    default: return state;
  }
}

const reducers = combineReducers({
  reducer
})

const store = createStore(
  reducers
);


const updateContactInfo = (payload) => ({
  type: 'CONTACT_INFO', payload
})

const mapStateToProps = state => {
  return {
    contactInfo: state.reducer.contactInfo
  }
}

const mapDispatchToProps = dispatch => ({
  updateContactInfo: payload => dispatch(updateContactInfo(payload))
})

class _App extends Component {

  constructor(props) {
    super(props);

    this.state = {...this.props}
    
    this.updateContactInfo = this.props.updateContactInfo;
  }
  
  static getDerivedStateFromProps (props, state) {

    return {...props}
  }
  
  onChangeInfo ({target: {name, value}}, action) {
    const contactInfo = { contactInfo: {[name]: value}};

    if (action === 'CHANGE') {
      this.updateContactInfo(contactInfo);
    }

  }

  render() {
    const { firstName, address, city } = this.state.contactInfo;
    
    return <div>
       <div>
        <input placeholder=" " type="text" name='firstName' value={firstName} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
        <div className="placeholder"><span>First &amp; last name</span></div>
    </div>
    <div>
        <input placeholder=" " type="text" name="address" value={address} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
        <div className="placeholder"><span>Address</span></div>
    </div>
    <div>
        <input placeholder=" " type="text" name="city" value={city} onChange={(e) => this.onChangeInfo(e, 'CHANGE')} required id="" />
        <div className="placeholder"><span>City</span></div>
    </div>
    {JSON.stringify(this.state)}
    </div>
  }
}

const App = connect(mapStateToProps, mapDispatchToProps)(_App)

ReactDOM.render(
    <Provider store={store}>
      <App />
    </Provider>,
    document.getElementById('root')
  );
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-polyfill/7.10.1/polyfill.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="https://unpkg.com/redux@4.0.5/dist/redux.js"></script>
<script src="https://unpkg.com/react-redux@latest/dist/react-redux.js"></script>
<div id="root"></div>
Józef Podlecki
  • 10,453
  • 5
  • 24
  • 50
  • This is actually a big brain move. I like it... but can't get it working on my app. Redux state keeps overwriting previous state. I enter first name -> contactInfo: { firstName: test} then I enter address -> contactInfo: {address: test2}. firstName isn't stored. – fosho Jun 10 '20 at 17:08
  • Updated snippet. Now the state should have similar structure to yours – Józef Podlecki Jun 10 '20 at 17:30
  • Thanks for your answer. Quick question,do I need set contactInfo as state or could I do this directly through redux without contactInfo: {...this.props}. Not sure what exactly that is needed for. – fosho Jun 10 '20 at 21:09
0

Great question.

There are a few things I have to point out before I give you an answer. There is generally 2 types of state that exists in any application: long lived state and short lived (a.k.a ephemeral) state. Redux is meant to serve you as a container to place all your long term states that is of general concern to potentially different parts of your application.

With that said, I can see that the only thing you are doing in your app is updating the state with the user input. I bet you then use that state to do something when the user clicks a submit button. If that is your case, the inputs by definition are ephemeral and I would NOT place the input states in Redux at all. Instead, I'd only fire 1 action when the user submits the form.

<form onSubmit={onSubmitHandler}>
  <input name="name" type="text" />
  <input name="hobby" type="text" />
  <button type="submit" />
<form />

------
// this is slight pseudo-code, but hopefully you get the gist
const onSubmitHandler = (event) => {
 const myFields = // get fields from event object.
 dispatch({type: 'SOME-ACTION', fields: myFields})
}

I'd also advise you to consider changing how you are generally modelling your actions. You can watch this video that goes over what I mean.

alaboudi
  • 3,187
  • 4
  • 29
  • 47
  • I'm using onChange, there is no submit button for the form since there are a lot of other moving parts. I will keep this in mind though, thanks. – fosho Jun 10 '20 at 17:28