1
votes

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, incrementCounter, decrementCounter } from '../actions.js'


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

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

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

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

    this.props.saveSelectValue(data)
  }

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

    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, counter) }}>Remove</button>
      </div>
    )
  }

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

    return (
      <div>

        {this.props.counter <= 4 &&
          <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,
    counter: state.EligibleAbout.counter,
  }
}

const EligibleAbout = connect(mapStateToProps, {saveSelect, removeSelect, saveSelectValue, incrementCounter, decrementCounter})(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 const INCREMENT_COUNTER = 'INCREMENT_COUNTER'
export const DECREMENT_COUNTER = 'DECREMENT_COUNTER'

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}
}

export function incrementCounter(data) {
    return { type: INCREMENT_COUNTER, data }
}

export function decrementCounter(data) {
    return { type: DECREMENT_COUNTER, data }
}

reducer.js

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

function EligibleAbout(state = { ids: [], counter: 0, 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
      }
    case INCREMENT_COUNTER:
      return {
        ...state,
        counter: state.counter + 1
      }
    case DECREMENT_COUNTER:
      return {
        ...state,
        counter: state.counter - 1
      }
    default:
      return state
  }
}

const FormApp = combineReducers({
  EligibleAbout
})

export default FormApp
2
but it seems to be updating all the dropdowns at once (not individually). What do you mean by this?BravoZulu

2 Answers

5
votes

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
  }
}
1
votes

You are only storing one selected value that they all read from. You'll want to give each select an ID of some sort and save / recall it's selected state specifically. Something like:

   case SAVE_SELECT_OPTION:
      return {
        ...state,
        selectedValues: {
          [action.selectID]: action.data
        }
      }

Of course you'll need to update your dispatch and actions accordingly.