update react state of nested object with array - reactjs

I want to insert a new object inside the options which is an array property inside the select1 object every time the user clicks on add button, how can I achieve this target, pls reply to this query.
I want to build a simple application where a user enters the options in the select box and can apply conditions based upon the selected option.
const [select1, setSelect1] = useState({
id: uuid(),
type: 'select',
properties: {
label: 'Select1',
options: [
{
id: uuid(),
label: 'text1',
value: {
labelTxt: 'text1',
childOptions: [],
},
},
{
id: uuid(),
label: 'text2',
value: {
labelTxt: 'text2',
childOptions: [],
},
},
],
},
parentId: null,
});
function addOptionsVal() {
setSelect1((prevState) => {
const options = [...prevState.properties.options];
options.splice(1, 0, {
id: uuid(),
label: optionVal,
value: {
labelTxt: optionVal,
childOptions: [],
},
});
console.log(options);
return { ...prevState, options };
});
}
return (
<div>
<select name="" id="">
<option value="">--select--</option>
{select1.properties.options.map((option) => {
return <option>{option.label}</option>;
})}
</select>
</div>
<input
type="text"
value={optionVal}
onChange={(e) => handleValueChange(e)}
/>
<button onClick={addOptionsVal}>Add options</button>
</div>
<div>
<input
type="checkbox"
value="male"
checked={checked}
onChange={toggleCondition}
id="condition"
/>
<label htmlFor="condition">Apply condition</label>
</div>
)
This is the entire code file in case you need

Try this:
function addOptionsVal() {
setSelect1((prevState) => {
return {
...prevState,
properties: {
...prevState.properties,
options: [
...prevState.properties.options,
{
id: uuid(),
label: optionVal,
value: {
labelTxt: optionVal,
childOptions: [],
},
},
],
},
};
});
}
stackblitz
Also, take a look at this question Correct modification of state arrays in React.js

Related

I want to save just value in react-select

I have some trouble with my react-select: When I click 'Submit', it save an object that have both 'value' and 'label' like this:
enter image description here
All I need is when I choose, it's show label list, and when I submit, it save only value. What can I do? Here are my code:
const [mainLang, setMainLang] = useState("");
const mainLangOptions = [
{ value: 'vi', label: 'Vietnamese' },
{ value: 'en', label: 'English' },
{ value: 'zh', label: 'Chinese' },
{ value: 'ja', label: 'Japanese' },
{ value: 'de', label: 'German' },
];
//This is Select part
<Select
onChange={(e) =>setMainLang(e)}
options={mainLangOptions}
/>
You need to set the option value as the mainLangOptions.value and the label as
mainLangOptions.label. By doing that you will display the label as option labels and save the value as the value of option tag. Check out the code below :
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor() {
super();
this.state = {
mainLanguage: ""
};
}
onOptionChangeHandler = (event) => {
this.state.mainLanguage = event.target.value;
console.log(this.state.mainLanguage);
};
render() {
const mainLangOptions = [
{ value: "vi", label: "Vietnamese" },
{ value: "en", label: "English" },
{ value: "zh", label: "Chinese" },
{ value: "ja", label: "Japanese" },
{ value: "de", label: "German" }
];
return (
<div>
<select onChange={this.onOptionChangeHandler}>
<option>Please choose one option</option>
{mainLangOptions.map((option, index) => {
return (
<option value={option.value} key={index}>
{option.label}
</option>
);
})}
</select>
</div>
);
}
}
export default App;
You must use e.target.value inside your setMainLang
<Select onChange={(e) => setMainLang(e.target.value)}>
This should work for your code but if it doesn't work, here is a complete code that I have tested in code sandbox and you can try it.
import React, { useState } from "react";
import { Select } from "#chakra-ui/react";
const Users = () => {
const [mainLang, setMainLang] = useState("");
const mainLangOptions = [
{ value: "vi", label: "Vietnamese" },
{ value: "en", label: "English" },
{ value: "zh", label: "Chinese" },
{ value: "ja", label: "Japanese" },
{ value: "de", label: "German" }
];
return (
<>
<Select onChange={(e) => setMainLang(e.target.value)}>
{mainLangOptions.map((op) => (
<option value={op.value}>{op.label}</option>
))}
</Select>
<h1>{mainLang}</h1>
</>
);
};
export default Users;

How to clear a select field when another select field changes with react-select

So I have 2 select fields. Then first select field is for categories; The other one is for subcategories. When the first field changes, the second field changes the list of options to choose, but the selected value of the second select field is still there. So I was wondering if there's a way to deselect the second value, when the first value changes.
Code:
import { useEffect, useState } from 'react';
// import categoryOptions from './categoryOptions';
import subcategoryOptions from './subcategoryOptions.json';
import Select from 'react-select';
const Info = ({
register,
errors,
product,
setValue,
getValues,
watchAllFields,
formStep,
unregister,
}) => {
const categories = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
const [selectedCategory, setSelectedCategory] = useState(null);
const [selectedSubcategory, setSelectedSubcategory] = useState(null);
const [subcategoryArray, setSubcategoryArray] = useState();
const [isChanged, setIsChanged] = useState(false);
useEffect(() => {
setSelectedSubcategory(null);
if (selectedCategory) {
const foundSubcategory = subcategories.filter(
(item) => item.category === selectedCategory.value
);
if (foundSubcategory) {
console.log(foundSubcategory);
setSubcategoryArray(foundSubcategory);
}
}
setIsChanged(true);
}, [selectedCategory]);
const subcategories = [
{ value: '', label: '⠀' },
{ value: 'eee', label: 'Chocolate', category: 'chocolate' },
{ value: 'e', label: 'zre', category: 'chocolate' },
{ value: 'es', label: 'Chooo', category: 'chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
return (
<section className='px-10'>
<div className='flex flex-col'>
<label htmlFor='title' className='pb-5 text-2xl text-white'>
Title
</label>
<input
type='text'
name='title'
className='text-white bg-indigo-900 border-indigo-900 input focus:bg-indigo-500'
placeholder='Try something creative (Required)'
maxLength={30}
{...register('title', {
required: {
value: true,
message: 'Title is required!',
},
})}
/>
{errors.title && (
<p className='mt-2 text-sm text-yellow-500'>{errors.title.message}</p>
)}
<h1 className='pt-10 pb-5 text-2xl text-white'>Gig Requierments</h1>
<textarea
type='text'
name='Requirements'
className='h-56 text-white bg-indigo-900 border-indigo-900 input focus:bg-indigo-500'
{...register('requirements')}
></textarea>
<h1 className='pt-10 pb-5 text-2xl text-white'>Category</h1>
<Select
defaultValue={selectedCategory}
onChange={setSelectedCategory}
options={categories}
/>
<Select
isClearable
defaultValue={selectedSubcategory}
onChange={setSelectedSubcategory}
options={subcategoryArray}
/>
</div>
</section>
);
};
export default Info;
Any help would be appreciated. Thanks.
Just pass a custom handler to the first Select that resets the second Select's value, if the currently selected subcategory is not in the new category:
const isSubcategory = (subcategory, category) => { /* returns true if subcategory is a subcategory of category */ }
const handleCategoryChange = useCallback(value => {
if (!isSubcategory(selectedSubcategory, value)) {
setSelectedSubcategory(null);
}
setSelectedCategory(value);
}, [selectedSubcategory])
<Select
isClearable
defaultValue={selectedCategory}
onChange={handleCategoryChange}
options={subcategoryArray}
/>
If the selected subcategory is never a subcategory of another category you can even skip the check.
The effect is not required. It all depends on local state changes.

useState hook losing map data on page refresh

I am using useState hook for my add product form.
When I refresh my page, data is not displaying for the field category (I am trying to display categories, so the user can select category from the list to create a product for that category). But! Data keeping in redux store:
enter image description here
It only shows when I go to another page using react router() and then go back.
This is my code:
export const AddProduct = () => {
const dispatch = useDispatch();
const userId = useSelector(state => state.auth.userId);
const categories = useSelector(state => state.categories.categories);
const [avatar, setAvatar] = React.useState('');
React.useEffect(() => {
dispatch(categoriesActions.fetchCategories());
}, [dispatch]);
const [orderForm, setOrderForm] = React.useState({
title: {
elementType: 'input',
label: 'Title',
elementConfig: {
type: 'text',
placeholder: 'Title'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
price: {
elementType: 'input',
label: 'Price',
elementConfig: {
type: 'text',
placeholder: 'Price'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
description: {
elementType: 'textarea',
label: 'Description',
elementConfig: {
type: 'text',
placeholder: 'Description'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
category: {
elementType: 'select',
label: 'Select category',
elementConfig:
categories.map(category => (
<option key={category._id} value={category.title}>
{category.title}
</option>))
,
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
});
const [formIsValid, setFormIsValid] = React.useState(false);
const addProductData = event => {
event.preventDefault();
const formData = {};
for (let formElementIdentifier in orderForm) {
formData[formElementIdentifier] = orderForm[formElementIdentifier].value;
}
const product = {
userId: userId,
title: formData.title,
price: formData.price,
description: formData.description,
category: formData.category
}
dispatch(productsActions.addProduct(product));
}
const inputChangedHandler = (event, inputIdentifier) => {
const updatedFormElement = updateObject(orderForm[inputIdentifier], {
value: event.target.value,
valid: checkValidity(
event.target.value,
orderForm[inputIdentifier].validation
),
touched: true
});
const updatedOrderForm = updateObject(orderForm, {
[inputIdentifier]: updatedFormElement
});
let formIsValid = true;
for (let inputIdentifier in updatedOrderForm) {
formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
}
setOrderForm(updatedOrderForm);
setFormIsValid(formIsValid);
};
const formElementsArray = [];
for (let key in orderForm) {
formElementsArray.push({
id: key,
config: orderForm[key]
});
}
let form = (
<form onSubmit={addProductData}>
{formElementsArray.map(formElement => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
label={formElement.config.label}
hint={formElement.config.hint}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={event => inputChangedHandler(event, formElement.id)}
/>
))}
<Button btnType="Success" disabled={!formIsValid}>ORDER</Button>
</form>
)
return (
<div class="wrapper">
<Header />
<article class="main">
<div class="row">
<div class="item--1-4 image-block">
<div class="product-image-group">
<img class="product-image-big" src={`/${avatar}`} />
<hr class="border-divider" />
<input type="file" onChange={e => setAvatar(e.target.files[0].name)} name="imageUrl" id="imageUrl" />
</div>
</div>
<div class="item--3-4">
<div class="item-title">
<h3>Add product</h3>
<hr class="border-divider" />
</div>
{form}
</div>
</div>
</article>
<LeftMenu />
</div>
)
}
This is line in my code related to that select field:
elementConfig:
categories.map(category => (
<option key={category._id} value={category.title}>
{category.title}
</option>))
That's because the first time that component loads, there is nothing in the categories and when the categories are set, you're not setting the orderForm data again. this is called stale props you need to do this:
useEffect(() => {
setOrderForm((oldValue) => ({
...oldValue,
category: {
...oldValue.category,
elementConfig: categories.map((category) => (
<option key={category._id} value={category.title}>
{category.title}
</option>
)),
},
}));
}, [categories])
This way every time categories data is changed you changing the orderForm state accordingly

Select form field not populating with options from state in ReactJS

This is my select function
import React from "react";
const Select = ({ name, label, options, error, ...rest }) => {
return (
<div className="form-group">
<label htmlFor={name}>{label}</label>
<select {...rest} id={name} name={name} className="form-control">
<option value="" />
{options.map((option) => (
<option key={option.id} value={option.id}>
{option.name}
</option>
))}
</select>
{error && <div className="alert alert-danger">{error}</div>}
</div>
);
};
export default Select;
This is the component state
state = {
data: {
vat: "",
city: "",
country: "",
mobile_number: "",
address: "",
has_conference: false,
star_rating: "",
},
errors: {},
hotel_type: [],
};
This function to populate data in the hotel_type
populateHotel_Types = () => {
let hotel_type = [...this.state.hotel_type];
hotel_type = [
{ id: "hotel", value: "hotel", name: "Hotel" },
{ id: "apartment", value: "apartment", name: "Apartment" },
{ id: "villa", value: "villa", name: "Villa" },
];
this.setState({ hotel_type });
};
And Finally this is the render function
{this.renderSelect(
"hotel_type",
"Hotel Type",
this.state.hotel_type
)}
Render select function
renderSelect(name, label, options) {
const { data, errors } = this.state;
return (
<Select
options={options}
name={name}
value={data[name]}
label={label}
onChange={this.handleChange}
error={errors[name]}
/>
);
}
Now i am struggling to get the data populated in the renderselect function. I am quite new to react and i am actually assuming this might be a silly question therefore kindly bear with me. What could be wrong with this code. Please help. Thanks
I think in first place, you have a problem here:
populateHotel_Types = () => {
let hotel_type = [...this.state.hotel_type];
hotel_type = [
{ id: "hotel", value: "hotel", name: "Hotel" },
{ id: "apartment", value: "apartment", name: "Apartment" },
{ id: "villa", value: "villa", name: "Villa" },
];
this.setState({ hotel_type });
};
Here, you are filling hotel_type with your state. And below, you are redefining the array, so you will have just this 3 new objects. So should do this to have the full list:
populateHotel_Types = () => {
const hotel_type = [
...this.state.hotel_type,
{ id: "hotel", value: "hotel", name: "Hotel" },
{ id: "apartment", value: "apartment", name: "Apartment" },
{ id: "villa", value: "villa", name: "Villa" },
];
this.setState({ hotel_type });
};
I suspected this was a silly question and indeed it was. I was forgetting to run the function populateHotel_Types in the componentDidMount function. Therefore the state was not being updated appropriately. I am leaving this here so that any newbie like myself will get an answer to such a scenario

Cannot use multiple radiogroups in react-materialize

So, I am building an avatar creator for my application, and I have a Carousel that holds multiple RadioGroups, I can slide to the new radiogroup, but when I click on any of the other radio groups (except for the first one) and it will only target the value from the first one.
<Carousel options={{ fullWidth: true, indicators: true }} className="black-text center">
<div>
<RadioGroup
name="hair"
label="Hair Selection"
value={this.state.avatar.topType}
onChange={this.handleAvatarChange}
options={[{ label: 'Eyepatch', value: 'Eyepatch' }, { label: 'Long
Hair', value: 'LongHairStraight2' }, { label: 'Medium Hair', value:
'LongHairNotTooLong' }, { label: 'Short Hair', value:
'ShortHairShortFlat' }, { label: 'Short Dreads', value:
'ShortHairDreads01' }, { label: 'Balding', value: 'ShortHairSides'
}]}
/>
</div>
<div>
<RadioGroup
name="hairColor"
label="Hair Selection"
value={this.state.avatar.hairColor}
onChange={this.handleAvatarChange}
options={[{ label: 'Brown', value: 'Brown' }, { label: 'Blonde',
value: 'Blonde' }, { label: 'Red', value:
'Red' }, { label: 'Gray', value:
'Gray' }, { label: 'Black', value:
'Black' }, { label: 'Auburn', value: 'Auburn'
}]}
/>
</div>
</Carousel>
handleAvatarChange = (event) => {
let selection = event.target.value;
let type = event.target.name;
console.log(selection, 'event')
console.log(type, 'type');
let avatarCopy = { ...this.state.avatar };
if (type === 'hair') {
avatarCopy.topType = selection
this.setState({ avatar: avatarCopy });
} else if (type === 'hairColor') {
avatarCopy.hairColor = selection;
this.setState({ avatar: avatarCopy });
}
}
I expect that when I slide to the next slide on the carousel, that I can change the state based on the type that was passed back to my handleAvatarChange function. Instead, all of the slides are changing the values based on the first slide.
It seems like the autogenerated id numbering for the options resets to 0 for each radiogroup, even if they are in the same DOM.
Therefore, each n input of the radio groups has the same id (<input id="radio0" type="radio" value="1" checked="">).
I have reported the bug here.
As a workaround, I'm requesting an "id" prop, and if not found, concatenating the component's name to the index to build the id's.
var RadioGroup = function RadioGroup(_ref) {
var onChange = _ref.onChange,
withGap = _ref.withGap,
disabled = _ref.disabled,
name = _ref.name,
radioClassNames = _ref.radioClassNames,
value = _ref.value,
options = _ref.options;
return _react.default.createElement(_react.default.Fragment, null, options.map(function (radioItem, idx) {
return _react.default.createElement("label", {
className: radioClassNames,
htmlFor: radioItem.id ? radioItem.id : "radio".concat(radioItem.name).concat(idx),
key: radioItem.id ? radioItem.id : "radio".concat(radioItem.name).concat(idx)
}, _react.default.createElement("input", {
id: radioItem.id ? radioItem.id : "radio".concat(radioItem.name).concat(idx),
value: radioItem.value,
type: "radio",
checked: radioItem.value === value,
name: name,
onChange: onChange,
disabled: disabled,
className: (0, _classnames.default)({
'with-gap': withGap
})
}), _react.default.createElement("span", null, radioItem.label));
}));
};

Resources