I've been following a YouTube tutorial on how to add or remove input fields dynamically with React.
Most of the tutorial was easy to follow, I almost achieved what I wanted to do but I have a problem when I add an object, it duplicates itself instead of adding the different values.
Here is the code:
import React, { useState } from "react";
import { Button, Form } from "react-bootstrap";
const countries = [
{
key: "1",
name: "",
value: ""
},
{
key: "2",
name: "Afghanistan",
value: "Afghanistan"
},
{
key: "3",
name: "Albania",
value: "Albania"
},
{
key: "4",
name: "Algeria",
value: "Algeria"
},
{
key: "5",
name: "Angola",
value: "Angola"
},
{
key: "6",
name: "Argentina",
value: "Argentina"
},
]
const listanswers = [
{
key: '01',
name: '',
value: '',
},
{
key: '02',
name: 'Yes',
value: 'Yes',
},
{
key: '03',
name: 'No',
value: 'No',
},
{
key: '04',
name: 'Unsure',
value: 'Unsure',
},
];
export default function Section1_3() {
const [question1_7, setQuestion1_7] = useState("");
const [instance1_7, setInstance1_7] = useState([
{
Country: "",
Answer: "",
}
]);
// handle input change
const handleInputChange = (instance, setinstance, e, index) => {
const { name, value } = e.target;
const list = [...instance];
list[index][name] = value;
setinstance(list);
};
// handle click event of the Remove button
const handlRemoveInstance = (instance, setinstance, index) => {
const list = [...instance];
list.splice(index, 1);
setinstance(list);
};
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, instance[0]]);
};
return (
<div>
<Form>
<Form.Group size="lg" controlId="formSection3">
{instance1_7.map((answer, i) => {
return (
<div key={(i + 1) * 3}>
<Form.Label>Instance variable 1</Form.Label>
{console.log(answer)}
<Form.Control
name="Country"
as="select"
onChange={e =>
handleInputChange(instance1_7, setInstance1_7, e, i)
}
>
{countries.map(country => (
<option
key={country.key}
value={country.value}
label={country.name}
/>
))}
</Form.Control>
<Form.Label>Instance variable 2</Form.Label>
<Form.Control
name="Answer"
as="select"
onChange={e =>
handleInputChange(instance1_7, setInstance1_7, e, i)
}
>
{listanswers.map(answer => (
<>
<option
key={answer.key}
value={answer.value}
label={answer.name}
/>
</>
))}
</Form.Control>
{instance1_7.length !== 1 && (
<Button
variant="danger"
onClick={() =>
handlRemoveInstance(instance1_7, setInstance1_7, i)
}
>
Remove
</Button>
)}
{instance1_7.length - 1 === i && (
<Button
variant="success"
onClick={() =>
handleAddInstance(instance1_7, setInstance1_7, i)
}
>
Add
</Button>
)}
</div>
);
})}
</Form.Group>
</Form>
<div style={{ marginTop: 20 }}>{JSON.stringify(instance1_7)}</div>
</div>
);
}
I don't know how to explain it properly, so I created a StackBlitz here : https://stackblitz.com/edit/react-dm6jwd?file=src%2FApp.js
Also if you have any suggestion on how to implement easily with a third party package that could be nice.
Thanks!
Edit:
Found solution, I added the array of the object instead of putting the object directly in the HandleAddInput function
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, {
Country: "",
Answer: "",
});
};
Update: I just realised you'd posted your solution. :o) What follows is an explanation of what the problem was.
Would you take a look at your code on line 86?
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, instance[0]]);
};
You'll see that there is a copying action on instance[0]. In javascript, when you pass an object as a copy, the object is passed as a reference. Once you change the values on the newly added object, it will update values on other references too.
If you intend to copy you will need to create a clone.
There are multiple ways of doing this. For instance, you could use the JSON API to remove referencing:
JSON.parse(JSON.stringify(instance[0]))
This is slower than:
Object.assign({}, instance[0])
Benchmark: https://www.measurethat.net/Benchmarks/Show/2471/2/objectassign-vs-json-stringparse#latest_results_block
A closer look at Object.assign(): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
Or, maybe you could require a function provided by lodash called cloneDeep and import in the following way:
import cloneDeep from 'lodash/cloneDeep';
And then:
setinstance([...instance, cloneDeep(instance[0]]));
In closing; I would suggest using the native assign method Object.assign({}, instance[0]).
// handle click event of the Add button
const handleAddInstance = (instance, setinstance) => {
setinstance([...instance, Object.assign({}, instance[0])]);
};
Best of luck :o)
Related
I have a .js file containing the code for a context menĂ¹ component:
const ContextMenuDialog = (props) => {
// my state declaration, other const, etc...
const build_ITEMS_ContextMenu = () => {
const A = [
{
key: "0",
label: "AA123BB",
disabled: true
},
{
key: "1",
label: "Show"
},
{
key: "2",
label: "Edit"
},
{
key: "3",
label: "Save"
}
];
return A;
};
return (
<div>
{loading ? (
"Loading"
) : (
<Menu
title="Menu right click"
style={{ top: 10, left: 10 }}
onClick={my_onClick_function}
items={ build_ITEMS_ContextMenu }
/>
)}
</div>
)
}
export default ContextMenuDialog;
Just consider that I cannot simply past the code of const A directly inside the "items" element; if I do it,the code works properly. In the real life I need to build the const A with a my algorithm.
This code doesn't work, the context menĂ¹ is not shown!
How can I solve this problem?
the problem is that you are not calling the function.
try this items={ build_ITEMS_ContextMenu() }
I receive an array like this from backend:
[
{
id: 0,
name: "John",
language: "Enlgish"
},
{
id: 1,
name: "Chris",
language: "Spanish"
},
{
id: 2,
name: "Bastian",
language: "German"
}
]
So I display the languages from this array in a table, and to do that I map through them.
I don't want to show the first language on the first object of this array
Parent.js
const [language, setLanguage] = useState ([])
useEffect(() => {
axios
.get('api').then((res) => {setLanguage(response.data.languages)})
}, [])
Child.js
return(
{language.map((lang, i) => {
return (
<tr key={"item-" + i}>
<td>
<div>
<input
type="text"
value={
lang.language
? lang.language.shift()
: lang.language
}
</div>
</td>
</tr>
))}
)
So what I have tried by far is the shift method which removes the first item of an array, but it didn't work.
This error happened :TypeError: lang.language.shift is not a function
How can I fix this?
Use the index
{language.map((lang, i) => {
(i > 0) && (
return (
......
I have multiple react-select inputs, each have their own separate options array. The issue i'm having is I'm now sure how to properly store the output of each select input.
I want to use this select output to POST to my backend but i'm unsure how to do it all with a single handler function.
This is what I have so far, i have 2 paragraphs to just output the appropriate result from the select fields but i can't seem to get it working.
This is the codesandbox i have:
https://codesandbox.io/s/busy-yonath-rlj9e?file=/src/App.js
In your code you are using the useState() hook and everytime a user selects an option, you are setting your state variable to the selected value.
The problem is that everytime you run the setSelectOptions({ e }) function, you are overwriting the existing data with the value of 'e'.
What you can do is:
Create a selectHandler function that takes in 2 arguments (the new value and the value it corresponds to in the state variable)
The code will look something like this:
import "./styles.css";
import Select from "react-select";
import { useEffect, useState } from "react";
export default function App() {
const [selectOptions, setSelectOptions] = useState({
brand: "",
model: "",
color: "",
bodyType: "",
fuelType: "",
transmission: "",
location: ""
});
const brandOptions = [
{ value: "bmw", label: "Bmw" },
{ value: "audi", label: "Audi" },
{ value: "toyota", label: "Toyota" },
{ value: "nissan", label: "Nissan" }
];
const transmissionOptions = [
{ value: "automatic", label: "Automatic" },
{ value: "manual", label: "Manual" }
];
useEffect(() => console.log(selectOptions), [selectOptions])
const handleChange = (e, type) => {
setSelectOptions((previousState) => ({
...previousState,
[type]: e.value,
}));
};
return (
<div className="App">
selected brand: {selectOptions.brand}
<br />
selected transmission:{selectOptions.transmission}
<div className="mb-2" style={{ width: "40%", margin: "0 auto" }}>
<Select
value={selectOptions.brand}
onChange={(e) => handleChange(e, "brand")}
placeholder="Select brand"
options={brandOptions}
/>
<Select
value={selectOptions.transmission}
onChange={(e) => handleChange(e, "transmission")}
placeholder="Select transmission"
options={transmissionOptions}
/>
</div>
</div>
);
}
Just as an explanation, all I am doing in the setSelectOptions() function is passing in the previous values of the state variable and updating the value coinciding to the select field.
Note: Insert this code into your project, I ran it and it worked so let me know if it did help!
I have several productOptionTypes with dynamic values. My problem is how do I match it with its variants. I want to get the variant id based on it.
Pls check codesandbox here CLICK HERE
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
Best I could come up with is this:
Update the option mapping to return the option name, to be used as the foreign key into the variants array objects, i.e. "Color", "Size", etc.
options={item.optionValues.map((option) => {
return {
label: option,
value: option,
name: item.optionName
};
})}
Update the variantType state to be an object, and update the handleSelectVariant handler to store the variant types by the foreign key "name" and the selected "value"
const [variantType, setSelectedVariant] = useState({});
const handleSelectVariant = (value, index) => {
setSelectedVariant((state) => ({
...state,
[value.name]: value.value
}));
};
Use a filter and every function to reduce the option types and values to a filtered result of variants that can be easily mapped to the id properties.
const matches = variants
.filter((variant) => {
return [
["option1Value", "option1Type"],
["option2Value", "option2Type"],
["option3Value", "option3Type"],
["option4Value", "option4Type"],
["option5Value", "option5Type"],
["option6Value", "option6Type"],
["option7Value", "option7Type"],
["option8Value", "option8Type"],
["option9Value", "option9Type"],
["option10Value", "option10Type"]
].every(([key, valueKey]) =>
variantType[variant[valueKey]]
? variant[key] === variantType[variant[valueKey]]
: true
);
})
.map(({ id }) => id);
Demo
When i understand it correctly you want to safe every selected value, then compare the result to your variants and select the variantId of the item matching all selctboxes based on it?
index.js
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import Select from "react-select";
import "./styles.css";
import { productOptionTypes } from "./productOptionTypes";
import { variants } from "./variants";
function App() {
const [variantType, setSelectedVariant] = useState({});
const [result, setResult] = useState(null);
const mapIndexKey = {
0: "option1Value",
1: "option2Value",
2: "option3Value",
3: "option4Value"
};
useEffect(() => {
setResult(
variants.filter(
(el) =>
el.option1Value == variantType.option1Value &&
el.option2Value == variantType.option2Value &&
el.option3Value == variantType.option3Value &&
el.option4Value == variantType.option4Value
)
);
}, [variantType]);
useEffect(() => {
console.log(result);
}, [result]);
const handleSelectVariant = (value, index) => {
setSelectedVariant({ ...variantType, [mapIndexKey[index]]: value.value });
console.log(variantType, value, index);
};
return (
<div className="App">
<form>
{productOptionTypes.map((item, index) => (
<>
<span className="title">{item.optionName}</span>
<Select
placeholder={`${item?.optionValues?.length} ${item.optionName}s`}
options={item.optionValues.map((option) => {
return { label: option, value: option };
})}
isSearchable={false}
onChange={(value) => handleSelectVariant(value, index)}
/>
</>
))}
</form>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
variants.js
export const variants = [
{
id: "60451fd290aeb720d96d8459",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01524blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "XS",
option3Type: "Toe Type",
option3Value: "Half Toe",
option4Value: "Bellarina",
retailPrice: "16.0",
__typename: "Variant"
},
{
id: "60451fd790aeb720d96d8463",
name: "Women's Bellarina Half Toe Grip Yoga Pilates Barre Socks",
wholesalePrice: "8.0",
sku: "s01525blk",
option1Type: "Color",
option1Value: "Black",
option2Type: "Size",
option2Value: "S",
option3Type: "Toe Type",
option3Value: "Half Toe",
retailPrice: "16.0",
__typename: "Variant"
}
}
Basicly what i do is saving all selected values in a map and everytime a value is changed just comparing it to all variants, if a variant is equal i select it.
For one variant i added the fourth key "Bellarina".
I hope this solution solves your problem.
When you want to verify the solution just select the first value of every selectbox and have a look at the console.
I'm new to react and trying to learn on my own. I started using react-select to create a dropdown on a form and now I'm trying to pass the value of the option selected. My state looks like this.
this.state = {
part_id: "",
failure: ""
};
Then in my render
const {
part_id,
failure
} = this.state;
My form looks has 2 fields
<FormGroup>
<Label for="failure">Failure</Label>
<Input
type="text"
name="failure"
placeholder="Failure"
value={failure}
onChange={this.changeHandler}
required
/>
</FormGroup>
<FormGroup>
<Label for="part_id">Part</Label>
<Select
name="part_id"
value={part_id}
onChange={this.changeHandler}
options={option}
/>
</FormGroup>
the changeHandler looks like this
changeHandler = e => {
this.setState({ [e.target.name]: e.target.value });
};
The change handler works fine for the input but the Select throws error saying cannot read property name. I went through the API docs and came up with something like this for the Select onChange
onChange={part_id => this.setState({ part_id })}
which sets the part_id as a label, value pair. Is there a way to get just the value? and also how would I implement the same with multiselect?
The return of react-select onChange event and the value props both have the type as below
event / value:
null | {value: string, label: string} | Array<{value: string, label: string}>
So what the error means is that you can't find an attribute of null (not selected), or any attributes naming as name (you need value or label)
For multiple selections, it returns the sub-list of options.
You can find the related info in their document
const options = [
{ value: 'chocolate', label: 'Chocolate' },
{ value: 'strawberry', label: 'Strawberry' },
{ value: 'vanilla', label: 'Vanilla' },
];
Update
For your situation (single selection)
option having type as above
const option = [
{value: '1', label: 'name1'},
{value: '2', label: 'name2'}
]
state save selected value as id
changeHandler = e => {
this.setState({ part_id: e ? e.value : '' });
};
pick selected option item via saved id
<Select
name="part_id"
value={option.find(item => item.value === part_id)}
onChange={this.changeHandler}
options={option}
/>
For multiple selections
state save as id array
changeHandler = e => {
this.setState({ part_id: e ? e.map(x => x.value) : [] });
};
pick via filter
<Select
isMulti // Add this props with value true
name="part_id"
value={option.filter(item => part_id.includes(item.value))}
onChange={this.changeHandler}
options={option}
/>
onChange function is a bit different in react-select
It passes array of selected values, you may get first one like
onChange={([selected]) => {
// React Select return object instead of value for selection
// return { value: selected };
setValue(selected)
}}
I have tried the above solutions but some of these solutions does update the state but it doesn't gets rendered on the Select value instantly.
Herewith a demo example:
this.state = {
part_id: null,
};
handleUpdate = (part_id) => {
this.setState({ part_id: part_id.value }, () =>
console.log(`Option selected:`, this.state.part_id)
);
};
const priceOptions = [
{ value: '999', label: 'Item One' },
{ value: '32.5', label: 'Item Two' },
{ value: '478', label: 'Item Three' }
]
<Select
onChange={this.handleUpdate}
value={priceOptions.find(item => item.value === part_id)}
options={priceOptions}
placeholder={<div>Select option</div>}
/>