Updating dropdown from redux state in componentDidMount() - javascript

I'm a beginner in react-redux. I've implemented the dependant dropdown functionality in react component.(Country => State => City).
I want to update dropdown values when I receive data from redux state.
I am getting redux state in this.props.dropdownForm and based on that calling changeCountry, changeState, changeCity functions where I'm setting the selected country based on condition.
What problem I'm facing is this piece of code is not working:
componentDidMount() {
if (this.props.dropdownForm) {
this.changeCountry();
this.changeState();
// this.changeCity();
}
}
but when I put above code in setTimeout then it works updates the country dropdown
componentDidMount() {
setTimeout(() => {
if (this.props.dropdownForm) {
this.changeCountry();
this.changeState();
// this.changeCity();
}
}, 100);
}
but that too is not consistent, means sometime it works sometime it doesn't.
Not getting this weird behaviour.
Below is my code:
import React from 'react';
import { connect } from 'react-redux';
import { Col, FormGroup, Label, Input } from 'reactstrap';
import { createStructuredSelector } from 'reselect';
import { getDropdownData } from '../../redux/profile/profile.selectors';
class Dropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
countries: [
{ name: 'Germany', states: [{ name: 'A', cities: ['Duesseldorf', 'Leinfelden-Echterdingen', 'Eschborn'] }] },
{ name: 'Spain', states: [{ name: 'B', cities: ['Barcelona'] }] },
{ name: 'USA', states: [{ name: 'C', cities: ['Downers Grove'] }] },
{ name: 'Mexico', states: [{ name: 'D', cities: ['Puebla'] }] },
]
};
this.changeCountry = this.changeCountry.bind(this);
this.changeState = this.changeState.bind(this);
this.changeCity = this.changeCity.bind(this);
}
componentDidMount() {
if (this.props.dropdownForm) {
this.changeCountry();
// this.changeState();
// this.changeCity();
}
}
changeCountry(event) {
let countryVal = !event && this.props.dropdownForm ? this.props.dropdownForm.country : event.target.value;
this.setState({ selectedCountry: countryVal });
const result = this.state.countries.find(cntry => {
return cntry.name === countryVal
});
result ? this.setState({ states: result.states }) : this.setState({ states: null });
this.setState({ cities: null });
}
changeState(event) {
let stateVal = !event && this.props.statePropSelected ? this.props.statePropSelected : event.target.value;
this.setState({ selectedState: stateVal });
const stats = this.state.countries.find(cntry => {
return cntry.name === this.state.selectedCountry
}).states;
const result = stats.find(stat => stat.name === stateVal);
result ? this.setState({ cities: result.cities }) : this.setState({ cities: null });
}
changeCity(e) {
this.props.onChangeCity(e);
}
render() {
let country = this.state.selectedCountry;
let state = this.state.selectedState;
return (
<>
<FormGroup row>
<Col md="4">
<Label htmlFor={this.props.countryProp} className="required">{this.props.countryProp}</Label>
</Col>
<Col xs="12" md="8">
<Input type="select" name="country" id={this.props.countryProp} placeholder={this.props.countryProp} value={country} onChange={this.changeCountry}>
<option>Select Country</option>
{this.state.countries.map((e, key) => {
return <option key={key}>{e.name}</option>;
})}
</Input>
</Col>
</FormGroup>
<FormGroup row>
<Col md="4">
<Label htmlFor={this.props.stateProp} className="required">{this.props.stateProp}</Label>
</Col>
<Col xs="12" md="8">
<Input type="select" name="state" id={this.props.stateProp} placeholder={this.props.stateProp} value={state} onChange={this.changeState}>
<option>Select State</option>
{
this.state.states ? this.state.states.map((e, key) => {
return <option key={key}>{e.name}</option>;
}) : null
}
</Input>
</Col>
</FormGroup>
<FormGroup row>
<Col md="4">
<Label htmlFor={this.props.cityProp} className="required">{this.props.cityProp}</Label>
</Col>
<Col xs="12" md="8">
<Input type="select" name="city" id={this.props.cityProp} placeholder={this.props.cityProp} onChange={this.changeCity}>
<option>Select City</option>
{
this.state.cities ? this.state.cities.map((e, key) => {
return <option key={key}>{e}</option>;
}) : null
}
</Input>
</Col>
</FormGroup>
</>
)
}
}
const mapStateToProps = createStructuredSelector({
dropdownForm: getDropdownData
});
export default connect(mapStateToProps)(Dropdown);
Selector:
import { createSelector } from 'reselect';
const dropdown = (state) => {
return state.profile.items.personal_details;
};
export const getDropdownData = createSelector(
[dropdown],
(data) => data
);

In this case its better to use componentDidUpdate instead of componentDidMount. Since you are looking for an update of an specific property it's a good option.
Change this:
componentDidMount() {
if (this.props.dropdownForm) {
this.changeCountry();
// this.changeState();
// this.changeCity();
}
}
To this:
componentDidUpdate() {
if (this.props.dropdownForm && !this.state.countriesChanged) {
this.changeCountry();
this.setState({ countriesChanged: true });
// this.changeState();
// this.changeCity();
}
}
Edit note:
You may add a field in the state called "countriesUpdated" that works as a flag and set it to true when it execute that block.

Related

How to change Antd form initialValues depends at url or id?

I got same component with Antd form for add/edit article. With pathes in router
<Route path="/add" component={ !currentUser ? Login : ArticleEditor } />
<Route path="/article/:id/edit" component={ !currentUser ? Login : ArticleEditor } />
When I click "edit" button I add initialValues to form, than if I click "Create new article" url changes to "/add", but form didn't update values. Values remains from edited article. How to update form values? Tried to set initialValues depends at path, or "id" but its not worked. How to update antd form values in that case?
const initialValues = this.props.location.pathname === '/add' ? {} : {
title: this.props?.title,
body: this.props?.body,
description: this.props?.description
};
Here you can see the component code - codesandbox link
The main issue with the code is form fields are not reset when url is changed, you can detect path change in shouldComponentUpdate and set isLoading to true and rest should work.
Updating initialValues will not work because, antd does shallow compare and once initialValues are set, you will not be able to change them.
There was an issue in the logic of componentDidUpdate which I corrected as well.
import React from "react";
import ErrorsList from "../ErrorsList/ErrorsList";
import userService from "../../services/userService";
import { connect } from "react-redux";
import { push } from "react-router-redux";
import { Form, Input, Button } from "antd";
import { store } from "../../store";
import actionCreators from "../../actionCreators";
const formItemLayout = {
labelCol: { span: 24 },
wrapperCol: { span: 24 }
};
const formSingleItemLayout = {
wrapperCol: { span: 24, offset: 0 }
};
const mapStateToProps = (state) => ({
...state.editor
});
const mapDispatchToProps = (dispatch) => ({
onLoad: (payload) => dispatch(actionCreators.doEditorLoaded(payload)),
onUnload: () => dispatch(actionCreators.doEditorUnloaded()),
onUpdateField: (key, value) =>
dispatch(actionCreators.doUpdateFieldEditor(key, value)),
onSubmit: (payload, slug) => {
dispatch(actionCreators.doArticleSubmitted(payload));
store.dispatch(push(`/`)); //article/${slug}
},
onRedirect: () => dispatch(actionCreators.doRedirect())
});
class ArticleEditor extends React.Component {
constructor(props) {
super(props);
this.id = this.props.match.params.id;
const updateFieldEvent = (key) => (e) =>
this.props.onUpdateField(key, e.target.value);
this.changeTitle = updateFieldEvent("title");
this.changeDescription = updateFieldEvent("description");
this.changeBody = updateFieldEvent("body");
this.changeTagInput = updateFieldEvent("tagInput");
this.isLoading = true;
this.submitForm = () => {
const article = {
title: this.props.title,
description: this.props.description,
body: this.props.body,
tagList: this.props.tagInput.split(",")
};
const slug = { slug: this.props.articleSlug };
const promise = this.props.articleSlug
? userService.articles.update(Object.assign(article, slug))
: userService.articles.create(article);
this.props.onSubmit(promise, this.props.articleSlug);
};
}
componentDidUpdate(prevProps, prevState) {
if (this.props.match.params.id !== prevProps.match.params.id) {
if (prevProps.match.params.id) {
this.props.onUnload();
}
this.id = this.props.match.params.id;
if (this.id) {
return this.props.onLoad(userService.articles.get(this.id));
}
this.props.onLoad(null);
}
this.isLoading = false;
}
componentDidMount() {
if (this.id) {
this.isLoading = true;
return this.props.onLoad(userService.articles.get(this.id));
}
this.isLoading = false;
this.props.onLoad(null);
}
componentWillUnmount() {
this.props.onUnload();
}
shouldComponentUpdate(newProps, newState) {
if (this.props.match.params.id !== newProps.match.params.id) {
this.isLoading = true;
}
return true;
}
render() {
const { errors } = this.props;
const initialValues = {
title: this.props?.title,
body: this.props?.body,
description: this.props?.description,
tags: this.props?.tagList
};
return this.isLoading ? (
"loading..."
) : (
<div className="editor-page">
<div className="container page">
<div className="">
<div className="">
<ErrorsList errors={errors}></ErrorsList>
<Form
{...formItemLayout}
initialValues={initialValues}
onFinish={this.submitForm}
>
<Form.Item
label="Title"
name="title"
placeholder="Article Title"
rules={[
{
required: true,
message: "Please input article title"
}
]}
>
<Input onChange={this.changeTitle} />
</Form.Item>
<Form.Item
label="Description"
name="description"
placeholder="Short description"
rules={[
{
required: true,
message: "Please input article description"
}
]}
>
<Input onChange={this.changeDescription} />
</Form.Item>
<Form.Item
name="body"
label="Article Text"
placeholder="article text"
>
<Input.TextArea onChange={this.changeBody} />
</Form.Item>
<Form.Item name="tags" label="Tags" placeholder="Enter tags">
<Input onChange={this.changeTagInput} />
</Form.Item>
<Form.Item {...formSingleItemLayout}>
<Button
className="editor-form__btn"
type="primary"
htmlType="submit"
disabled={this.props.inProgress}
>
Submit Article
</Button>
</Form.Item>
</Form>
</div>
</div>
</div>
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ArticleEditor);
take a look at this forked codesandbox.
You have to clean the fields before you re-use the 'ArticleEditor' component. Here you are using the same component for two different route, hence it's not changing.
You have to check if you are editing or adding a new entry to the Editor. Your editor component may look like this then,
const ArticleEditor = props => {
const [form] = Form.useForm();
useEffect(() => {
if (props.match.params.id) form.setFieldsValue({value : 'Some values'})
else form.resetFields()
}, [props?.match?.params]);
return (
<Form form={form} onFinish={yourFinishMethod}>
//...your form fields
</Form>
)
}

get the response of a POST API in React

I have a POST API that when executed needs to render the response result in a div
I am using React.js and Redux
This is the service where the api is stored
const addMP3Request = async (payload) => {
const formData = new FormData();
formData.append("titre",payload.payload.titre);
formData.append("description",payload.payload.description);
formData.append("type",payload.payload.type);
formData.append("file1",payload.payload.fichier1);
const wsResponse = await axios.request({
method: 'post',
url: `http://localhost:3000/api/watson_tones/analyzeMP3`,
data: formData,
config: {
headers: {
'Content-Type': `multipart/form-data`,
'Accept': 'application/json',
}
}
});
return wsResponse;
}
this is my reducer
const INIT_STATE = {
responseData: ''
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case ADD_MP3: {
return {
...state,
responseData: action.responseData
}
}
default:
return state;
}
}
this is the action :
export const addMP3Action = (titre,description,type,fichier1) => {
return {
type: ADD_MP3,
payload: {
titre: titre,
description: description,
type: type,
fichier1:fichier1
},
};
};
and this is the view where I am adding the MP3 and wants to store the response of this api
import React, { Component } from "react";
import { Button, Form, Input, Select} from "antd";
import { connect } from "react-redux";
import {addMP3Action} from "../../appRedux/actions/Mp3";
import SweetAlert from "react-bootstrap-sweetalert";
import AudioPlayer from "react-h5-audio-player";
const FormItem = Form.Item;
const Option = Select.Option;
class AjoutExtrait extends Component {
constructor() {
super();
this.state = {
selectedFileUrl: '',
selectedFileName:"",
showPlayer:false,
alertMessage: "",
alertSuccess: false,
alertWarning: false,
titre:'',
description:'',
type:"",
responseData:''
};
this.onFileChange = this.onFileChange.bind(this)
this.handleChangeTitre = this.handleChangeTitre.bind(this)
this.handleChangeDescription = this.handleChangeDescription.bind(this)
this.handleChangeType = this.handleChangeType.bind(this)
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.responseData !== prevState.responseData && nextProps.responseData) {
//console.log('responseDataaa',nextProps.responseData)
return { responseData: nextProps.responseData };
} else
//console.log('responseDataaa',nextProps.responseData)
return null;
}
onFileChange(e) {
this.setState({ file: e.target.files[0] });
this.setState({ selectedFileUrl: URL.createObjectURL(e.target.files[0]) });
this.setState({ showPlayer: true });
this.setState({ selectedFileName: e.target.files[0].name });
}
handleChangeTitre(event) {
this.setState({titre: event.target.value});
}
handleChangeDescription(event) {
this.setState({description: event.target.value});
}
// handleChangeType(event) {
// this.setState({type: event.target.value});
// }
handleChangeType = (value) => {
let selectedFilter = value;
this.setState({type: selectedFilter});
}
handleFormSubmit = (e) => {
e.preventDefault();
this.props.form.validateFieldsAndScroll((err, values) => {
if (!err) {
this.props.addMP3Action(this.state.titre,this.state.description,this.state.type,this.state.file);
}
});
}
onConfirmAlertMessage = () => {
this.setState({
alertMessage: "",
alertSuccess: false,
alertWarning: false,
});
};
renderAlertMessage(){
var intl = this.props.intl;
const { alertSuccess, alertWarning, alertMessage } = this.state;
return(
<div>
<SweetAlert
show={alertSuccess}
success
title={alertMessage !== "" ?(intl.formatMessage({id: alertMessage})):""}
onConfirm={this.onConfirmAlertMessage}>
</SweetAlert>
<SweetAlert show={alertWarning}
warning
title={alertMessage !== "" ?(intl.formatMessage({id: alertMessage})):""}
onConfirm={this.onConfirmAlertMessage}>
</SweetAlert>
</div>
);
}
render() {
// const { getFieldDecorator } = this.props.form;
console.log("gfgfg",this.props.responseData)
const formItemLayout = {
labelCol: {
xs: { span: 24 },
sm: { span: 24 },
md: { span: 12 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 24 },
md: { span: 12 },
},
};
const tailFormItemLayout = {
wrapperCol: {
xs: { span: 24 },
sm: { span: 24 },
md: { span: 20 },
},
};
return (
<div ref={this.props.scrollDivAjoutExtrait} className="cm-comedien-mp3-card-ajout">
<h2>Ajouter un Extrait MP3</h2>
<p> gfdffd {this.props.responseData}</p>
<Form onSubmit={this.handleFormSubmit}>
<FormItem {...formItemLayout}>
<p>Titre</p>
<Input value={this.state.titre} onChange={this.handleChangeTitre}/>
</FormItem>
<FormItem {...formItemLayout}>
<p>Description</p>
<Input value={this.state.description} onChange={this.handleChangeDescription}/>
</FormItem>
<FormItem {...formItemLayout}>
<p>Type</p>
<Select
// name="type"
// value={this.state.type}
defaultValue=""
onChange={this.handleChangeType}
>
<Option value="1">type 1</Option>
<Option value="2">type 2</Option>
</Select>
</FormItem>
<FormItem {...formItemLayout}>
<p>Upload fichier MP3</p>
<div className="cm-comedien-mp3-ajout-file-container">
<input style={{ opacity: "0",display:"none" }}
type="file"
id="file"
name="file"
title="Choisir un fichier mp3"
accept=".mp3"
onChange={this.onFileChange}
/>
<div class="cm-comedien-mp3-ajout-file-name">
<span style={{ paddingRight: "12px" }}>
{this.state.selectedFileName}
</span>
</div>
<div class="cm-comedien-mp3-ajout-file-button">
<label for="file">
upload
</label>
</div>
</div>
</FormItem>
{this.state.showPlayer ?
<FormItem {...formItemLayout}>
<AudioPlayer
type="audio/mp3"
position="inline"
nomComedien=""
titre={this.state.selectedFileName}
fileName={this.state.selectedFileName}
url={this.state.selectedFileUrl}
/>
</FormItem>
:null}
<FormItem {...tailFormItemLayout}>
<Button type="primary" htmlType="submit">
Ajouter
</Button>
</FormItem>
</Form>
<div style={{width:"100%",height:"400px",background:"white",marginBottom:"50px"}}>
</div>
{this.renderAlertMessage()}
</div>
);
}
}
const AjoutExtraitForm = Form.create()(AjoutExtrait);
const mapStateToProps = ({ mp3 }) => {
const {
responseData
} = mp3;
return {
responseData
}
};
export default connect(mapStateToProps, { addMP3Action })(AjoutExtraitForm);
When i console.log(this.props.responseData) I get undefined Do you have any idea ?
I believe your issue is that in your reducer, action has no property responseData. Remember that your action returns an object with properties type and payload. When you pass it to your reducer to update the state, you need to look into action.payload to access the data that was sent in the action.
For example, you might want your reducer to look more like this:
const INIT_STATE = {
responseData: ''
};
export default (state = INIT_STATE, action) => {
switch (action.type) {
case ADD_MP3: {
return {
...state,
responseData: action.payload
}
}
default:
return state;
}
}
You can always refer to the documentation for more specifics:
https://redux.js.org/basics/basic-tutorial

Use of React getDerivedStateFromProps()

I'm exploring React and am somewhat confused over lifecycle methods and parent-child communication. Specifically, I'm trying to create a component which wraps a select element and adds an input box when the "Other" option is selected. I have implemented this using getDerivedStateFromProps() but according to the documentation this lifecycle method should rarely be used. Hence my question: is there another pattern I should be aware of and use in this case?
This is my code, the value and options are passed down as props, as is the handleChange() method of the parent component. So when changes are made in the select or input elements, the parent component state is updated first and a new value is passed down through props.value.
export default class SelectOther extends Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
}
static getDerivedStateFromProps(props) {
let optionIndex = -1;
for (let i = 0; i < props.options.length; i++) {
if (props.options[i].value === props.value) {
optionIndex = i;
break;
}
}
if (optionIndex > -1) {
return {
selected: props.options[optionIndex].value,
text: "",
showInput: false
};
} else {
return {
selected: "",
text: props.value,
showInput: true
};
}
}
handleChange(e) {
this.props.handleChange({
"target": {
"name": this.props.name,
"value": e.target.value
}
});
}
render() {
return (
<div>
<label>{ this.props.label }</label>
<select name={ this.props.name } value={ this.state.selected } onChange={ this.handleChange }>
{
this.props.options.map(option => <option key={option.value} value={option.value}>{option.label}</option>)
}
<option value="">Other</option>
</select>
{
this.state.showInput &&
<div>
<label>{ this.props.label } (specify other)</label>
<input type="text" className="form-control" value={ this.state.text } onChange={ this.handleChange }></input>
</div>
}
</div>
)
}
}
You can simplify by not having SelectOther have any state, here is an example of how you can pass a function that dispatches an action to change values. Because SelectOther is a pure component it won't needlessly re render:
//make this a pure component so it won't re render
const SelectOther = React.memo(function SelectOther({
label,
name,
value,
options,
handleChange,
}) {
console.log('in render',name, value);
const showInput = !options
.map(o => o.value)
.includes(value);
return (
<div>
<label>{label}</label>
<select
name={name}
value={showInput ? '' : value}
onChange={handleChange}
>
{options.map(option => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
<option value="">Other</option>
</select>
{showInput && (
<div>
<label>{label} (specify other)</label>
<input
type="text"
name={name}
className="form-control"
value={value}
onChange={handleChange}
></input>
</div>
)}
</div>
);
});
const App = () => {
//create options once during App life cycle
const options = React.useMemo(
() => [
{ value: 'one', label: 'one label' },
{ value: 'two', label: 'two label' },
],
[]
);
//create a state to hold input values and provide
// a reducer to create new state based on actions
const [state, dispatch] = React.useReducer(
(state, { type, payload }) => {
//if type of action is change then change the
// payload.name field to payload.value
if (type === 'CHANGE') {
const { name, value } = payload;
return { ...state, [name]: value };
}
return state;
},
//initial state for the inputs
{
my_name: '',
other_input: options[0].value,
}
);
//use React.useCallback to create a callback
// function that doesn't change. This would be
// harder if you used useState instead of useReducer
const handleChange = React.useCallback(
({ target: { name, value } }) => {
dispatch({
type: 'CHANGE',
payload: {
name,
value,
},
});
},
[]
);
return (
<div>
<SelectOther
label="label"
name="my_name"
value={state.my_name}
options={options}
handleChange={handleChange}
/>
<SelectOther
label="other"
name="other_input"
value={state.other_input}
options={options}
handleChange={handleChange}
/>
</div>
);
};
//render app
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I could have uses useState in App but then I have to use useEventCallback or do a useState for every input value. The following documentation comes up with the useEventCallback pattern and then immediately after states that we don’t recommend this pattern so that's why I came up with the useReducer solution instead.

Reactjs - retrieve attributes from event.target

I have a component which renders Input type='select': (I am using reactstrap)
import React, {Component} from 'react'
import {
Form,
FormGroup,
Input,
Button,
Col,
} from 'reactstrap'
import {
withRouter,
} from 'react-router'
import Context from '../../../../../provider'
class NewPost extends Component {
constructor(props) {
super(props)
this.state = {
subreddits: [],
subreddit_selected: '',
subreddit_id: 0,
...
}
this.handleSubredditSelect = this.handleSubredditSelect.bind(this)
}
componentDidMount() {
fetch('/api/reddit/r/')
.then(data => data.json())
.then(json => {
this.setState({
subreddits: json,
...
})
})
}
handleSubredditSelect(event) {
console.log('selected id: ',event.target.id)
this.setState({
subreddit_selected: event.target.value,
subreddit_id: event.target.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
...
render() {
return (
<Context.Consumer>
{context => {
return (
<React.Fragment>
<Form
...
>
<FormGroup row>
<Col sm={7}>
<Input
type="select"
onChange={this.handleSubredditSelect}
required
>
<option key='0' disabled selected>Select an Option</option>
{this.state.subreddits.map((subreddit) => {
return (
<option key={subreddit.id} id={subreddit.id}>{'r/' + subreddit.name}</option>
)
})}
</Input>
</Col>
</FormGroup>
...
</React.Fragment>
)
}}
</Context.Consumer>
)
}
}
export default withRouter(NewPost)
So, I have a function handleSubredditSelect which does the following:
handleSubredditSelect(event) {
this.setState({
subreddit_selected: event.target.value,
subreddit_id: event.target.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
In this function I am not getting any value for event.target.id.
I have tried event.target.key as well but that returned an empty string "".
I want to set subreddit_id in state to the selected option's ID
The selecting does not work because event.target in <select> element returns entire tree with options:
// result of event.target:
<select>
<option id="id-1" value="some1">some1</option>
<option id="id-2" value="some2">some2</option>
<option id="id-3" value="some3">some3</option>
</select>
Instead the selected one.
For accessing the current option element from select you should rely on selectedIndex:
event.target[event.target.selectedIndex].id
The code:
export default class SelectForm extends React.Component {
constructor(props) {
super(props);
this.state = {
value: "some1",
id: "id-1"
};
}
handleChange = event => {
console.log("event", event.target, event.target.selectedIndex);
this.setState({
value: event.target.value,
id: event.target[event.target.selectedIndex].id
});
};
render() {
return (
<div>
<select value={this.state.sex} onChange={this.handleChange}>
<option id="id-1" value="some1">some1</option>
<option id="id-2" value="some2">some2</option>
<option id="id-3" value="some3">some3</option>
</select>
ID: {this.state.id}
</div>
);
}
}
You can use selectedIndex attribute of select:
handleSubredditSelect(event) {
const selectedOption = event.target.childNodes[event.target.selectedIndex];
this.setState({
subreddit_selected: event.target.value,
subreddit_id: selectedOption.id,
}, () =>
this.props.history.push(`/${this.state.subreddit_selected}/new/`)
)
}
Here is the sandbox: https://codesandbox.io/s/n5679m5owj
AFAIK event.target.id should be working, doing the same in my project.
But should't it be
<Input
type="select"
onChange={(e) => this.handleSubredditSelect}
required>`
? (No parantheses after the methodname)

How to update the state of array of objects from form fields?

I have created a component that can be used for creating a new company record. A modal is opened with a form and the values are linked to the state values. In my situation, it will be possible to create more than one record of a company if the user chooses to add another company. A new company object will be pushed to the company state and the new empty form will be rendered.
This is what I've tried based on this answer:
import { Component } from 'react';
import { Modal, Header, Form, Button, Icon, Tab, Segment } from 'semantic-ui-react';
export default class CompanyCreate extends Component {
constructor(props) {
super(props);
this.state = {
company: [
{
name: '',
segment: ''
}
]
};
this.initialState = this.state;
this.handleChange = this.handleChange.bind(this);
this.handleCompanyChange = this.handleCompanyChange.bind(this);
}
handleChange = (e, { name, value }) => this.setState({ [name]: value });
handleCompanyChange = (e, { name, value }) => {
const index = this.state.company.findIndex((x) => {
return x[name] === value;
});
if (index === -1) {
console.log('error');
} else {
this.setState({
company: [
...this.state.company.slice(0, index),
Object.assign({}, this.state.company[index], value),
...this.state.company.slice(index + 1)
]
});
}
};
render() {
const { company } = this.state;
return (
<Segment>
{company.map((e, index) => (
<Form size="large" key={index}>
<Form.Group>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Nome"
placeholder="Nome"
name="name"
value={e.name}
required
/>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Segmento"
placeholder="Segmento"
name="segment"
value={e.segment}
required
/>
</Form.Group>
</Form>
))}
</Segment>
);
}
}
My problem is that I can't set the company state properly. How can you update the state in relation to the changes in the form fields?
Looking for answers, I found the package: immutability-helper. Based on this answer, the problem was solved simply and elegantly.
The solution:
import update from 'immutability-helper';
//...
this.state = {
company: [
{
name: '',
segment: ''
}
]
};
//...
handleCompanyChange = (e, { name, value, id }) => {
let newState = update(this.state, {
company: {
[id]: {
[name]: { $set: value }
}
}
});
this.setState(newState);
};
//...
render() {
const { company } = this.state;
return (
<Segment>
{company.map((e, index) => (
<Form size="large" key={index}>
<Form.Group>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Nome"
placeholder="Nome"
name="name"
value={e.name}
id={index}
required
/>
<Form.Input
width={6}
onChange={this.handleCompanyChange}
label="Segmento"
placeholder="Segmento"
name="segment"
value={e.segment}
id={index}
required
/>
</Form.Group>
</Form>
))}
</Segment>
);
}

Categories