John John -3 years ago 283
React JSX Question

React/Redux - save select value onChange

I've created a Component that lets you Add/Remove a dropdown fields (onClick of two buttons). I'm using Redux to keep the state of the dropdown so that when I navigate back and forth (React Router) the added dropdowns are not lost.

Now I also need to keep the values of each dropdown onChange. So onChange I'm sending the value of the dropdowns to the reducer but it seems to be updating all the dropdowns at once (not individually).

Any idea how to fix this?

Here is my code so far:

Component

import React from 'react'
import { connect } from 'react-redux'
import uuidV4 from 'uuid-v4'
import { saveSelect, removeSelect, saveSelectValue } from '../actions.js'


class AccountUpgrade extends React.Component {
constructor(props) {
super(props);
}

addInput = () => {
this.props.saveSelect(uuidV4())
}

removeInput = (index) => {
this.props.removeSelect(index)
}

saveSelectValue = (e) => {
let data = {}
data = e.target.value

this.props.saveSelectValue(data)
}

renderSelect = (id, index) => {
const selectedValue = this.props.selectedValue || ''

return(
<div className="col-12">
<select
key={id}
name={'document-'+ id}
value={selectedValue}
onChange = {this.saveSelectValue}
>
<option value="0">Please Select</option>
<option value="1">Australia</option>
<option value="2">France</option>
<option value="3">United Kingdom</option>
<option value="4">United States</option>
</select>

<button onClick={ () => {this.removeInput(index) }}>Remove</button>
</div>
)
}

render(){
const ids = this.props.ids || []

return (
<div>
<button onClick={ this.addInput }>Add</button>

<div className="inputs">
{ids.map(this.renderSelect)}
</div>
</div>
)
}
}

const mapStateToProps = (state) => {
return {
store: state.EligibleAbout,
ids: state.EligibleAbout.ids,
selectedValue: state.EligibleAbout.selectedValue,
}
}

const EligibleAbout = connect(mapStateToProps, {saveSelect, removeSelect, saveSelectValue})(AccountUpgrade)

export default EligibleAbout


action.js

export const ADD_SELECT = 'ADD_SELECT'
export const REMOVE_SELECT = 'REMOVE_SELECT'
export const SAVE_SELECT_OPTION = 'SAVE_SELECT_OPTION'

export function saveSelect(data) {
return { type: ADD_SELECT, data }
}

export function removeSelect(data) {
return { type: REMOVE_SELECT, data }
}

export function saveSelectValue(data) {
return { type: SAVE_SELECT_OPTION, data}
}


reducer.js

import { combineReducers } from 'redux'
import { ADD_SELECT, REMOVE_SELECT, SAVE_SELECT_OPTION } from './actions'

function EligibleAbout(state = { ids: [], selectedValue: 'Please select'}, action = {}){
switch (action.type){
case ADD_SELECT:
return {
...state,
ids: [].concat(state.ids, action.data),
}
case REMOVE_SELECT:
return {
...state,
ids: state.ids.filter((id, index) => (index !== action.data)),
}
case SAVE_SELECT_OPTION:
return {
...state,
selectedValue: action.data
}
default:
return state
}
}

const FormApp = combineReducers({
EligibleAbout
})

export default FormApp

Answer Source

Since you need to keep the selected value for each id or dropdown, the best approach would be keeping an object array with objects which have id and value property instead of having string array ids. So you will have to change your code into something like this. I didn't test the code, so let me know in comments if you get any issue with it.

class AccountUpgrade extends React.Component {
  constructor(props) {
    super(props);
  }

  addInput = () => {
    this.props.saveSelect({id:uuidV4()})
  }

  removeInput = (index) => {
    this.props.removeSelect(index)
  }

  saveSelectValue = (e, id) => {
    let data = {}
    data.id = id;
    data.value = e.target.value

    this.props.saveSelectValue(data)
  }

  renderSelect = (selection, index) => {
    const selectedValue = selection.value || '';
    const id = selection.id;

    return(
      <div className="col-12">
        <select
          key={id}
          name={'document-'+ id}
          value={selectedValue}
          onChange = {(e) => this.saveSelectValue(e, id)}
        >
          <option value="0">Please Select</option>
          <option value="1">Australia</option>
          <option value="2">France</option>
          <option value="3">United Kingdom</option>
          <option value="4">United States</option>
        </select>

        <button onClick={ () => {this.removeInput(index) }}>Remove</button>
      </div>
    )
  }

  render(){
    const selections = this.props.selections || []

    return (
      <div>
        <button onClick={ this.addInput }>Add</button>

        <div className="inputs">
          {selections.map(this.renderSelect)}
        </div>
      </div>
    )
  }
}

const mapStateToProps = (state) => {
  return {
    store: state.EligibleAbout,
    selections: state.EligibleAbout.selections,
  }
}

You need to modify your reducer as well.

function EligibleAbout(state = { selections: [] } , action = {}){
  switch (action.type){
    case ADD_SELECT:
      return {
        ...state,
        selections: [].concat(state.selections, action.data),
      }
    case REMOVE_SELECT:
      return {
        ...state,
        selections: state.selections.filter((selection, index) => (index !== action.data)),
      }
    case SAVE_SELECT_OPTION:
      return {
        ...state,
        selections: state.selections.map((selection) => selection.id === action.data.id ? action.data : selection)
      }
    default:
      return state
  }
}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download