React - Multiple components with the same actions & reducers - javascript

I created a component that lets you add/remove additional dropdowns onClick of a button. I use Redux to keep the state of the added fields and value selected.
It works fine but if I add the component twice on the page (using the same actions and reducers), both dropdowns will update at the same time.
How could I make them work independently?
index.jsx
import React from 'react'
import { connect } from 'react-redux'
import DropDownField from './form/drop-down-field'
import uuidV4 from 'uuid-v4'
import { saveSelect, removeSelect, saveSelectValue } from './actions.js'
class Component extends React.Component {
constructor(props) {
super(props);
}
saveData(e) {
let data = {}
data[e.target.name] = e.target.value
this.context.store.dispatch(
addData(data)
)
}
addInput = (e) => {
e.preventDefault()
this.props.saveSelect({id:uuidV4()})
}
removeInput = (index, e) => {
e.preventDefault()
this.props.removeSelect(index)
}
saveSelectValue = (e, id) => {
let data = {}
data.id = id
data.value = e.target.value
this.props.saveSelectValue(data)
}
renderNationalitiesSelect = (selection, index) => {
const selectedValue = selection.value || ''
const id = selection.id
return(
<div>
<DropDownField
key={id}
name={'field-'+ id}
value={selectedValue}
onChange = {(e) => { this.saveSelectValue(e, id) }}
required
options={{
0: 'Please Select',
1: 'British',
2: 'French',
3: 'American',
4: 'Australian'
}} />
<a href="#" onClick={ (e) => {this.removeInput(index, e) }}>Remove</a>
</div>
)
}
renderCountriesSelect = (selection, index) => {
const selectedValue = selection.value || ''
const id = selection.id
return(
<div>
<DropDownField
key={id}
name={'field-'+ id}
value={selectedValue}
onChange = {(e) => { this.saveSelectValue(e, id) }}
required
options={{
0: 'Please Select',
1: 'United Kingdom',
2: 'France',
3: 'United States',
4: 'Australia'
}} />
<a href="#" onClick={ (e) => {this.removeInput(index, e) }}>Remove</a>
</div>
)
}
render(){
const selections = this.props.selections || []
let {
Nationality,
CountryOfResidence
} = this.props.store
return (
<DropDownField name="Nationality" value={Nationality} options={{
0: 'Please Select', 1: 'British', 2: 'French', 3: 'American', 4: 'Australian'
}} onChange={this.saveData.bind(this)} />
<div>
<div>
{selections.map(this.renderNationalitiesSelect)}
</div>
{this.props.selections.length < 4 &&
<div>
<a href="#" onClick={this.addInput}>Add</a>
</div>
}
</div>
<DropDownField name="CountryOfResidence" value={CountryOfResidence} options={{
0: 'Please Select', 1: 'United Kingdom', 2: 'France', 3: 'United States', 4: 'Australia'
}} onChange={this.saveData.bind(this)} />
<div>
<div>
{selections.map(this.renderCountriesSelect)}
</div>
{this.props.selections.length < 4 &&
<div>
<a href="#" onClick={this.addInput}>Add</a>
</div>
}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
store: state.AddDropdown,
selections: state.AddDropdown.selections,
}
}
const AddDropdown = connect(mapStateToProps, {saveSelect, removeSelect, saveSelectValue})(Component)
export default AddDropdown
action.js
export const ADD_DATA = 'ADD_DATA'
export const ADD_SELECT = 'ADD_SELECT'
export const REMOVE_SELECT = 'REMOVE_SELECT'
export const SAVE_SELECT_OPTION = 'SAVE_SELECT_OPTION'
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 }
}
export function saveSelectValue(data) {
return { type: SAVE_SELECT_OPTION, data }
}
reducer.js
import ObjectAssign from 'object.assign'
import { combineReducers } from 'redux'
import { ADD_DATA, ADD_SELECT, REMOVE_SELECT, SAVE_SELECT_OPTION } from './actions'
function AddDropdown(state = { selections: []}, action = {}){
switch (action.type){
case ADD_DATA:
return ObjectAssign({}, state, action.data)
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
}
}
const FormApp = combineReducers({
AddDropdown
})
export default FormApp

I suggest isolating each set of dropdowns as a seperate component, then working on isolating each one's redux state. My library, redux-subspace was designed for this purpose.
index.jsx
import React from 'react'
import { connect } from 'react-redux'
import { SubspaceProvider } from 'redux-subspace'
import DropDownField from './form/drop-down-field'
import uuidV4 from 'uuid-v4'
import { saveSelect, removeSelect, saveSelectValue } from './actions.js'
class Component extends React.Component {
constructor(props) {
super(props);
}
saveData(e) {
let data = {}
data[e.target.name] = e.target.value
this.context.store.dispatch(
addData(data)
)
}
addInput = (e) => {
e.preventDefault()
this.props.saveSelect({id:uuidV4()})
}
removeInput = (index, e) => {
e.preventDefault()
this.props.removeSelect(index)
}
saveSelectValue = (e, id) => {
let data = {}
data.id = id
data.value = e.target.value
this.props.saveSelectValue(data)
}
renderSelections = (selection, index) => {
const selectedValue = selection.value || ''
const id = selection.id
return(
<div>
<DropDownField
key={id}
name={'field-'+ id}
value={selectedValue}
onChange = {(e) => { this.saveSelectValue(e, id) }}
required
options={this.props.options} />
<a href="#" onClick={ (e) => {this.removeInput(index, e) }}>Remove</a>
</div>
)
}
render(){
return (
<div>
<DropDownField name={this.props.name} value={this.props.store.value} options={this.props.options} onChange={this.saveData.bind(this)} />
<div>
{this.props.selections.map(this.renderSelections)}
</div>
{this.props.selections.length < 4 &&
<div>
<a href="#" onClick={this.addInput}>Add</a>
</div>
}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
store: state,
selections: state.selections,
}
}
const SingleAddDropdown = connect(mapStateToProps, {saveSelect, removeSelect, saveSelectValue})(Component)
const AddDropdown = () => {
return (
<div>
<SubspaceProvider mapState={state => state.nationality} namespace="nationalities">
<SingleAddDropdown name="Nationality" options={{
0: 'Please Select',
1: 'British',
2: 'French',
3: 'American',
4: 'Australian'
}}/>
</SubspaceProvider>
<SubspaceProvider mapState={state => state.countryOfResidence} namespace="countryOfResidence">
<SingleAddDropdown name="Country of Residence" options={{
0: 'Please Select',
1: 'United Kingdom',
2: 'France',
3: 'United States',
4: 'Australia'
}}/>
</SubspaceProvider>
</div>
)
}
export default AddDropdown
reducer.js
import ObjectAssign from 'object.assign'
import { combineReducers } from 'redux'
import { namespaced } from 'redux-subspace'
import { ADD_DATA, ADD_SELECT, REMOVE_SELECT, SAVE_SELECT_OPTION } from './actions'
function AddDropdown(state = { selections: []}, action = {}){
switch (action.type){
case ADD_DATA:
return ObjectAssign({}, state, action.data)
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
}
}
const FormApp = combineReducers({
namespaced(AddDropdown, "nationality"),
namespaced(AddDropdown, "countryOfResidence")
})
export default FormApp
NOTE: as per my comment there are some issues with this code and I have not attempted to clean them up for this example.

Related

Why doesn't the table appear?

components/App.js
import React from 'react';
import { connect } from 'react-redux'
import { add_task } from '../actions/index';
class App extends React.Component {
state = {
text: '',
time: ''
}
render_tasks = () => {
const { tasks } = this.props
return (
<table>
<tbody>
{tasks.map(task => {
return (
<tr key={task.id}>
<td>{task.text}</td>
<td>{task.time}</td>
</tr>
)
})
}
</tbody>
</table>
)
}
render() {
console.log('App props', this.props)
return (
<div className="App">
<input type='text'
onChange={(e) => this.setState({
text: e.target.value
})} />
<input type='time'
onChange={(e) => this.setState({
time: e.target.value
})} />
<button
onClick={() => this.props.add_task(
this.state.text, this.state.time)}>
Add
</button>
{this.render_tasks()}
</div>
);
}
}
function mapStateToProps(state) {
return {
tasks: state
}
}
function mapDispatchToProps(dispatch) {
return {
add_task : () => dispatch(add_task())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
actions/index.js
import { ADD_TASK } from './../types';
export const add_task = (text, time) => {
const action = {
type: ADD_TASK,
text,
time
}
console.log('Add action', action)
return action
}
reducers/index.js
import { ADD_TASK } from './../types';
const tasks = (state=[], action) => {
let tasks = [];
if (action.type === ADD_TASK) {
tasks = [...state, {
text: action.text,
time: action.time,
id: Math.random()
}]
console.log('Data from reducer', tasks)
return tasks
} else {
return state
}
}
export default tasks;
When I click the button I expect to get a table with the information I entered in the input fields, but nothing appears, I tried replacing the table part in render_tasks function in App.js with an unordered list and the map returns a list item including 2 spans one for the text and the other for the time but all I got is the dot of the list item!
In
add_task : () => dispatch(add_task())
You don't pass any arguments to add_task().
You can explicitly define the arguments:
add_task : (text, time) => dispatch(add_task(text, time))

Hide/show elements with react-select - elements not hiding

I am using the react-select library with a list of states, and then using that to hide/show my elements
The elements are showing when I select them from the list, but then don't hide when they're removed. Here is the code:
import React from 'react';
import Select from 'react-select';
const options = [
{ value: 'ny', label: 'NY' },
{ value: 'ca', label: 'California' },
{ value: 'ak', label: 'Arkansas' }
];
export default class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {}
}
handleChange = (selectedOption) => {
if (!selectedOption) {
this.setState({})
}
else {
const result = {}
selectedOption.map((option) => {
result[option.value] = true
})
this.setState(result)
}
}
render() {
return (
<div>
<Select
isMulti
onChange={this.handleChange}
options={options}
/>
{this.state.ny && (
<div>NY State Images</div>
)}
{this.state.ca && (
<div>CA State Images</div>
)}
{this.state.ak && (
<div>AK State Images</div>
)}
</div>
)
}
}
Something is strange with that React.Component.
Try to use functional component:
export default function HomePage() {
const [state, setState] = useState({});
const handleChange = selectedOption => {
console.log("CHANGE_HAPPEN: ", selectedOption);
if (!selectedOption) {
setState({});
} else {
const result = {};
selectedOption.forEach(option => {
result[option.value] = true;
});
console.log("RESULT: ", result);
setState(prev => result);
}
};
return (
<div>
<Select isMulti onChange={handleChange} options={options} />
{state.ny && <div>NY State Images</div>}
{state.ca && <div>CA State Images</div>}
{state.ak && <div>AK State Images</div>}
</div>
);
}

Why changes from local state don't go into global state?

To-Do-List
When I try to edit my created task, I see some modifications, but only in local State. When I look at the data of the global state, nothing change, the data remains the same as after creating the tasks object.
It is also interesting to note that when case EDIT_TASK has worked , action.id = values from Input, and action.task = undefined
P.S: Put all the component code below, maybe there was a mistake somewhere.
P.S: Sorry for ENG
Component's code
import React from 'react'
import s from "./../../App.module.css";
class Item extends React.Component {
state = {
statusChange: false,
task: this.props.task
}
activeStatusChange = () => {
this.setState( {
statusChange: true
}
);
}
deActivateStatusChange = () => {
this.setState( {
statusChange: false
}
);
this.props.editTask(this.props.task)
}
onStatusChange = (e) => {
this.setState({
task: e.target.value
})
}
render(){
return (
<div className={s.item}>
<span onClick={this.props.editStatus} className={s.statusTask}>
{this.props.status ? <img src="https://img.icons8.com/doodle/48/000000/checkmark.png"/>
: <img src="https://img.icons8.com/emoji/48/000000/red-circle-emoji.png"/>}
</span>
{ this.state.statusChange
? <input onChange={this.onStatusChange} autoFocus={true} onBlur={this.deActivateStatusChange} value={this.state.task} />
: <span className={this.props.status === true ? s.task : s.taskFalse} onClick={this.activeStatusChange}> {this.state.task} </span>}
<span onClick={this.props.deleteTask} className={s.close}><img src="https://img.icons8.com/color/48/000000/close-window.png"/></span>
</div>
)
}
}
export default Item;
Reducer's code
import React from 'react'
import shortid from 'shortid';
const ADD_TASK = 'ADD_TASK'
const EDIT_TASK = 'EDIT_TASK'
const initialState = {
tasks: []
};
const mainReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_TASK: {
return {
...state,
tasks: [{
id: shortid.generate(),
task: action.task,
status: false
}, ...state.tasks]
}
}
case EDIT_TASK: {
return {
...state,
tasks: state.tasks.filter((t) => t.id === action.id ? {...t, task: action.newTask} : t)
}
}
default:
return state
}
}
//window.store.getState().mainReducer.tasks
export const addTask = task => ({type: 'ADD_TASK', task});
export const editTask = (id,newTask) => ({type: 'EDIT_TASK', id, newTask})
export default mainReducer;
Parent's component:
import React from "react";
import s from "./../../App.module.css";
import CurrentTasks from "../current-tasks";
import FilterButtonTasks from "../filter-button-tasks";
import ListTasks from "../tasks-list";
class SetForm extends React.Component {
constructor(props) {
super(props);
this.state = {
text: ''
}
}
onInputChange = event => {
this.setState({
[event.target.name]: event.target.value
})
}
handleSubmit = event => {
event.preventDefault();
if(this.state.text === '') {
return undefined
}
this.props.addTask(this.state.text)
this.setState({
text: ''
})
}
filterTasks = (tasks, activeFilter) => {
switch (activeFilter) {
case 'done': {
return tasks.filter(task => task.status);
}
case 'active': {
return tasks.filter(task => !task.status)
}
default:
return tasks;
}
}
render() {
const currentTasks = this.filterTasks(this.props.tasks, this.props.filter);
return (
<div>
<form onSubmit={this.handleSubmit}>
<div>
<input name={"text"} onChange={this.onInputChange} value={this.state.text}placeholder={"Set your task"} className={s.setTask}/>
<button onClick={this.handleSubmit} className={s.add}>ADD</button>
<button onClick={this.props.removeAllTasks} className={s.clearAll}>Clear</button>
</div>
</form>
<CurrentTasks tasks={this.props.tasks}/>
<ListTasks currentTasks={currentTasks} editStatus={this.props.editStatus} deleteTask={this.props.deleteTask} editTask={this.props.editTask}/>
<FilterButtonTasks currentTasks={currentTasks} changeFilter={this.props.changeFilter} removeAllDone={this.props.removeAllDone}/>
</div>
)
}
}
export default SetForm;
one more:
import React from 'react'
import Item from './SetItem/item'
const ListTasks = ({currentTasks,editStatus,deleteTask,editTask}) => {
return (
currentTasks.map(t => (<Item editStatus={() => editStatus(t.id)}
deleteTask={() => deleteTask(t.id)}
key={t.id} task={t.task} status={t.status} editTask={editTask}/>))
)
}
export default ListTasks;
Since, you are only updating the local state onStatusChange the state does not get updated in global state. So on deActivateStatusChange you need to call this.props.editTask with updated state, that is this.state.task
deActivateStatusChange = () => {
this.setState({
statusChange: false
});
this.props.editTask(this.state.task); // change is here
};
The problem is in your EDIT_TASK reducer:
Change
state.tasks.filter((t) => t.id === action.id ? {...t, task: action.newTask} : t)
To
state.tasks.map((t) => t.id === action.id ? {...t, task: action.newTask} : t)
map will update the object, not filter
Code should be:
case EDIT_TASK: {
return {
...state,
tasks: state.tasks.map((t) => t.id === action.id ? {...t, task: action.newTask} : t)
}
}
Also it seems like you are not passing id and newTask to editTask action:
const ListTasks = ({ currentTasks, editStatus, deleteTask, editTask }) => {
return currentTasks.map(t => (
<Item
editStatus={() => editStatus(t.id)}
deleteTask={() => deleteTask(t.id)}
key={t.id}
task={t.task}
status={t.status}
editTask={(newTask) => editTask(t.id, newTask)} // change current code to this
/>
));
};

How to target specific element after mapping and passing onClick function as props

I am facing such problem, i got my array of records fetched from an API, mapped it into single elements and outputting them as single components. I have function which changes state of parent Component, passes value to child component and child component should hide/show div content after button is clicked.
Of course. It is working, but partially - my all divs are being hidden/shown. I have set specific key to each child component but it doesn't work.
App.js
import React, { Component } from 'react';
import './App.css';
import axios from 'axios';
import countries from '../../countriesList';
import CitySearchForm from './CitySearchForm/CitySearchForm';
import CityOutput from './CityOutput/CityOutput';
import ErrorMessage from './ErrorMessage/ErrorMessage';
class App extends Component {
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false
}
getCities = (e) => {
e.preventDefault();
const countryName = e.target.elements.country.value.charAt(0).toUpperCase() + e.target.elements.country.value.slice(1);
const countryUrl = 'https://api.openaq.org/v1/countries';
const wikiUrl ='https://en.wikipedia.org/w/api.php?action=query&prop=extracts&exintro&explaintext&format=json&category=city&redirects&origin=*&titles=';
const allowedCountries = new RegExp(/spain|germany|poland|france/, 'i');
if (allowedCountries.test(countryName)) {
axios
.get(countryUrl)
.then( response => {
const country = response.data.results.find(el => el.name === countryName);
return axios.get(`https://api.openaq.org/v1/cities?country=${country.code}&order_by=count&sort=desc&limit=10`)
})
.then( response => {
const cities = response.data.results.map(record => {
return { name: record.city };
});
cities.forEach(city => {
axios
.get(wikiUrl + city.name)
.then( response => {
let id;
for (let key in response.data.query.pages) {
id = key;
}
const description = response.data.query.pages[id].extract;
this.setState(prevState => ({
cities: [...prevState.cities, {city: `${city.name}`, description}],
infoMessage: prevState.infoMessage = ''
}))
})
})
})
.catch(error => {
console.log('oopsie, something went wrong', error)
})
} else {
this.setState(prevState => ({
infoMessage: prevState.infoMessage = 'This is demo version of our application and is working only for Spain, Poland, Germany and France',
cities: [...prevState.cities = []]
}))
}
}
descriptionTogglerHandler = () => {
this.setState((prevState) => {
return { visible: !prevState.visible};
});
};
render () {
return (
<div className="App">
<ErrorMessage error={this.state.infoMessage}/>
<div className="form-wrapper">
<CitySearchForm getCities={this.getCities} getInformation={this.getInformation} countries={countries}/>
</div>
{this.state.cities.map(({ city, description }) => (
<CityOutput
key={city}
city={city}
description={description}
show={this.state.visible}
descriptionToggler={this.descriptionTogglerHandler} />
))}
</div>
);
}
}
export default App;
CityOutput.js
import React, { Component } from 'react';
import './CityOutput.css';
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show } = this.props;
let descriptionClasses = 'output-record description'
if (show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
export default CityOutput;
Put the visible key and the toggle function in the CityOutput instead of having it in the parent
import React, { Component } from "react";
import "./CityOutput.css";
class CityOutput extends Component {
state = {
visible: true
};
descriptionTogglerHandler = () => {
this.setState({ visible: !this.state.visible });
};
render() {
const { city, description } = this.props;
let descriptionClasses = "output-record description";
if (this.state.visible) {
descriptionClasses = "output-record description open";
}
return (
<div className="output">
<div className="output-record">
<b>City:</b> {city}
</div>
<button onClick={() => this.descriptionTogglerHandler()}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
);
}
}
export default CityOutput;
There are two ways of how I would approach this,
The first one is setting in your state a key property and check and compare that key with the child keys like:
state = {
country: '',
error: false,
cities: [],
infoMessage: '',
visible: false.
currKey: 0
}
descriptionTogglerHandler = (key) => {
this.setState((prevState) => {
return { currKey: key, visible: !prevState.visible};
});
};
// then in your child component
class CityOutput extends Component {
render() {
const { city, descriptionToggler, description, show, currKey, elKey } = this.props;
let descriptionClasses = 'output-record description'
if (show && elKey === currKey) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={() => descriptionToggler(elKey)}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
The other way is to set an isolated state for every child component
class CityOutput extends Component {
constructor(props) {
this.state = {
show: false
}
}
function descriptionToggler() {
const {show} = this.state;
this.setState({
show: !show
})
}
render() {
const { city, descriptionToggler, description } = this.props;
let descriptionClasses = 'output-record description'
if (this.state.show) {
descriptionClasses = 'output-record description open';
}
return (
<div className="output">
<div className="output-record"><b>City:</b> {city}</div>
<button onClick={descriptionToggler}>Read more</button>
<div className={descriptionClasses}>{description}</div>
</div>
)
}
};
I hope this helps ;)

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

Categories

Resources