0

I'm trying to add and remove a dropdown field on click of two buttons:

  • one 'Add' button which adds/creates a new dropdown field (predefined with options & values)
  • A 'Remove' button next to each dropdown (in order to remove them individually)

This is a multi page app so I'm using React Router to navigate from one page to another - so to keep the state I'm using Redux.

I managed to add and remove the dropdown fields on click of the buttons - but I'm facing two issues:

  1. I'm falling to save the dropdowns to the store, so if I navigate to another page and come back, the added dropdowns are gone from the page
  2. Anytime a new dropdown is added, the option of the selects is reset

Here is my code so far:

import React from 'react'
import { connect } from 'react-redux'
import { addData, saveSelect, removeSelect } from '../actions.js'

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

    this.state = {
      inputSelect: []
    }
  }

  saveData = (e) => {
    let data = {}
    data[e.target.name] = e.target.value

    this.context.store.dispatch(
      addData(data)
    )
  }

  addInput = () => {
    const inputSelect = this.state.inputSelect.concat(this.renderDropDown);
    this.setState({ inputSelect });

    this.context.store.dispatch(
      saveSelect(this.state.inputSelect)
    )
  }

  removeInput = (item) => {
    let index = this.state.inputSelect.indexOf(item);
    let newInputSelect = this.state.inputSelect;
    newInputSelect.splice(index,1);

    this.setState({
      inputSelect: newInputSelect
    });

    this.context.store.dispatch(
      removeSelect(newInputSelect)
    )
  }

  renderDropDown = (el, index) => {
    return(
      <div>
        <select
          key={index}
          name={'document-'+ index}
          value={'document-'+ index}
          onChange = {this.saveData}
        >
          <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 }>Remove</button>
      </div>
    )
  }

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

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

class AccountUpgrade extends React.Component {

  constructor(props) {
    super(props);
  }

  continueClick() {
    this.context.router.push('/AccountUpgrade/Confirmation/')
  }

  render(){
    return (
      <div>
        <div className="row">
            <AddSelectField />

            <ButtonRow
            primaryProps={{
              children: 'Continue',
              onClick: this.continueClick.bind(this)
            }} />
        </div>
      </div>
    )
  }
}

AccountUpgrade.contextTypes = {
  store: React.PropTypes.object.isRequired,
  router: React.PropTypes.object.isRequired
}

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

const EligibleAbout = connect(mapStateToProps)(AccountUpgrade)

export default EligibleAbout

action.js

export const ADD_DATA = 'ADD_DATA'
export const ADD_SELECT = 'ADD_SELECT'
export const REMOVE_SELECT = 'REMOVE_SELECT'


export function addData(data) {
  return { type: ADD_DATA, data }
}


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

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

reducer.js

import ObjectAssign from 'object.assign'
import { combineReducers } from 'redux'
import { ADD_DATA, ADD_SELECT, REMOVE_SELECT } from './actions'

function EligibleAbout(state = {}, action){
  switch (action.type){
    case ADD_DATA:
      return ObjectAssign({}, state, action.data)
    case ADD_SELECT:
        return ObjectAssign({}, state, action.data)
    case REMOVE_SELECT:
      return ObjectAssign({}, state, action.data)
    default:
      return state
  }
}

const FormApp = combineReducers({
  EligibleAbout
})

export default FormApp

1 Answer 1

1

You are managing state in the component as well as in the reducer. In the render function, you are always taking values of inputSelect from the state which is blank initially. You are not using values stored in the reducer for rendering purpose that's why you are not getting it on coming back.

Do not store inputSelect in your component state. Just store that in reducer and use inputSelect from reducer for rendering purpose.

import React, { PropTypes } from 'react'
import { connect } from 'react-redux'
import uuidV4 from 'uuid/v4'


class AddSelectField extends React.Component {
  static propTypes = {
    ids: PropTypes.array,
    removeSelect: PropTypes.func,
    saveSelect: PropTypes.func,
  }
  static defaultProps = {
    ids: [],
  }
  saveData = (e) => {
    // Just do your stuff here.
  }

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

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

  renderDropDown = (id, index) => {
    return (
      <div>
        <select
          key={id}
          name={id}
          onChange={this.saveData}
          >
          <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.renderDropDown)}
        </div>
      </div>
    )
  }
}

const mapStateToProps = state => {
  const eligibleAbout = state.EligibleAbout
  return {
    ids: eligibleAbout.ids,
  }
}

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


export default class AccountUpgrade extends React.Component {
  continueClick() {
    // do your stuff here.
  }
  render() {
    return (
      <div>
        <div className="row">
          <EligibleAbout />

        </div>
      </div>
    )
  }
}

Updated Reducer is:

function eligibleAbout(state = { ids: [] }, action = {}){
  switch (action.type) {
    case ADD_DATA:
    // set according to requirement.
    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)),
      }
    default:
      return state
  }
}

If I had been at your place, I would do it like this except state management. I would recommend using immutable will be a good option for state management.

Note: I have only implemented adding and removing input functionality. Not clear about your saveData function requirement.

Sign up to request clarification or add additional context in comments.

7 Comments

Hi Ritesh, thanks for your answer. Any chance you could show me an example of what you mean in the code?
saveData is the function that keeps the state of the option selected. Looking at your code - just wondering why is uuid package needed. Not possible to do it without it?
I want to assign unique id to each select. UUID is only needed for that purpose.
If add data is only needed to set selected value,then just store the value corresponding to unique id of each select.
Could have used the value of 'key' from map instead of UUID no?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.