Get Id Based on Dynamic Selected Values in React - javascript

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.

Related

Checkbox values are not updating when state is set: ReactJS

I am having an app with two sections. Left section contains the categories and the right section containing the items under it. Under each category, I have the button to select all or unselect all items. I see the state changes happening in the code ( it is pretty printed inside HTML) but the checkbox values are not getting updated. Can someone help?
https://codesandbox.io/s/zealous-carson-dy46k8?file=/src/App.js
export const RightSection = ({ name, apps, json, setJson }) => {
function handleSelectAll(categoryName, type) {
const checked = type === "Select All" ? true : false;
const updated = Object.fromEntries(
Object.entries(json).map(([key, category]) => {
if (category.name !== categoryName) {
return [key, category];
}
const { name, tiles, ...rest } = category;
return [
key,
{
name,
...rest,
tiles: tiles.map((item) => ({
...item,
checked
}))
}
];
})
);
setJson(updated);
}
return (
<>
<div>
<input
type="button"
value={`select all under ${name}`}
onClick={() => handleSelectAll(name, "Select All")}
/>
<input
type="button"
value={`unselect all under ${name}`}
onClick={() => handleSelectAll(name, "Unselect All")}
/>
<h4 style={{ color: "blue" }}>{name} Items</h4>
{apps.map((app) => {
return (
<section key={app.tileName}>
<input checked={app.checked} type="checkbox" />
<span key={app.tileName}>{app.tileName}</span> <br />
</section>
);
})}
</div>
</>
);
};
import { useEffect, useState, useMemo } from "react";
import { SidebarItem } from "./SideBarItem";
import { RightSection } from "./RightSection";
import "./styles.css";
export default function App() {
const dummyJson = useMemo(() => {
return {
cat1: {
id: "cat1",
name: "Category 1",
tiles: [
{
tileName: "abc",
searchable: true,
checked: false
},
{
tileName: "def",
searchable: true,
checked: true
}
]
},
cat2: {
id: "cat2",
name: "Category 2",
tiles: [
{
tileName: "ab",
searchable: true,
checked: true
},
{
tileName: "xyz",
searchable: true,
checked: false
}
]
},
cat3: {
id: "cat3",
name: "Category 3",
tiles: [
{
tileName: "lmn",
searchable: true,
checked: true
},
{
tileName: "",
searchable: false,
checked: false
}
]
}
};
}, []);
const [json, setJson] = useState(dummyJson);
const [active, setActive] = useState(dummyJson["cat1"]);
return (
<>
<div className="container">
<div>
<ul>
{Object.values(json).map((details) => {
const { id, name } = details;
return (
<SidebarItem
key={name}
name={name}
{...{
isActive: id === active.id,
setActive: () => setActive(details)
}}
/>
);
})}
</ul>
</div>
<RightSection
name={active.name}
apps={active.tiles}
{...{ json, setJson }}
/>
</div>
<p>{JSON.stringify(json, null, 2)}</p>
</>
);
}
since you have not updated data of checkbox (in your code) / logic is wrong (in codesandbox) do the following add this function in RightSection
...
function setTick(app, value: boolean) {
app.checked = value;
setJson({...json})
}
...
and onChange in input checkbox
<input
onChange={({ target }) => setTick(app, target.checked)}
checked={app.checked}
type="checkbox"
/>
Codesandbox: see line 25 -> 28 and line 48 in RightSection.tsx are the lines I added
For the two buttons select all and unselect all to update the state of the checkboxes, the data must be synchronized (here you declare active as json independent of each other, this makes the update logic complicated. Unnecessarily complicated, please fix it to sync
const [json, setJson] = useState(dummyJson);
const [activeId, setActiveId] = useState('cat1');
const active = useMemo(() => json[activeId], [json, activeId]);
and update depends:
<SidebarItem
key={name}
name={name}
{...{
isActive: id === activeId,
setActive: () => setActiveId(id)
}}
/>
Codesandbox: line 60 -> 63 and line 81 -> 82 in file App.js
https://codesandbox.io/s/musing-rhodes-yp40fi
the handleOperation function could also be rewritten very succinctly but that is beyond the scope of the question

MUIv5 AutoComplete multi select customization

I am trying to use mui Autocomplete multi select component but can't make it work as per my requirements. I want it to have default selected values which will be passed as a props from Parent component and dropdown options which I will be getting via an API.
Problem 1 :
I am able to render my deafult values in input box which was passed as props but my selected values should not be there in options. Right now I can add duplicate values meaning even though I have few items selected by default in input box but still I could see them in dropdown. I have tried to read the docs it says The value must have reference equality with the option in order to be selected. but can't understand clearly. Is it because I am using 2 different states(arrays) for value and options?.
Problem 2 (Extra requirement)
In addition to the list of usersOptions that I will get from API to populate the dropdown option, I also need to add an option of All.
Whenever few items are already selected and then user clicks ALL in dropdown all the selected items should be removed and only All should be there in input box and when user tries to open the dropdown, all other values should be disabled.
When the user deselect ALL from the dropdown then only he/she should be able to select individual value(user)
Basically the user should be able to select only ALL or Individual user(value).
Link to CodeSanbox(https://codesandbox.io/s/naughty-elbakyan-hi16x5?file=/src/AutoComplete.js:0-1399)
import { useEffect, useState } from "react";
//This is the structure of data for both users & usersOptions
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
// fetch("https://x.com/api/getUsers")
// .then((response) => response.json())
// .then((data) => setUsersOptions(data));
//let's assume API returned this
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
const usersOptionsWithAll = [{ id: 0, name: "All" }, ...usersOptions];
setUsersOptions(usersOptionsWithAll);
}, []);
console.log("selectedUsers", selectedUsers);
console.log("usersOptions", usersOptions);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
setSelectedUsers(newValue);
}}
/>
);
};
export default AutoComplete;
[1]: https://i.stack.imgur.com/83V7d.jpg
1. You should use isOptionEqualToValue prop to determine if the option represents the given value.
2. It is preferable to use filterOptions prop to add 'All' option or any options you want to add.
import { Autocomplete, TextField, createFilterOptions } from "#mui/material";
import { useEffect, useState } from "react";
const AutoComplete = ({ users }) => {
const [selectedUsers, setSelectedUsers] = useState(users);
const [usersOptions, setUsersOptions] = useState([]);
useEffect(() => {
const usersOptions = [
{ id: 1, name: "User 1" },
{ id: 2, name: "User 2" },
{ id: 3, name: "User 3" },
{ id: 4, name: "User 4" },
{ id: 5, name: "User 5" },
{ id: 6, name: "User 6" }
];
setUsersOptions(usersOptions); // Set options without adds
}, []);
return (
<Autocomplete
multiple
id="tags-outlined"
options={usersOptions}
getOptionLabel={(option) => option.name}
filterSelectedOptions
renderInput={(params) => (
<TextField {...params} label="" placeholder="" />
)}
value={selectedUsers}
onChange={(event, newValue) => {
// Check if 'All' option is clicked
if (newValue.find(option => option.id === 0)) {
// Check if all options are selected
if (usersOptions.length === selectedUsers.length) {
setSelectedUsers([]);
} else {
setSelectedUsers(usersOptions);
}
} else {
setSelectedUsers(newValue);
}
}}
// Add These props
isOptionEqualToValue={(option, value) => option.id === value.id}
filterOptions={(options, params) => {
const filtered = createFilterOptions()(options, params);
return [{ id: 0, name: 'All' }, ...filtered];
}}
/>
);
};
export default AutoComplete;
Mui Autocomplete API

How would I be able to pass the product ID according to what product was selected?

I have a dynamic form where users can add multiple products. I wonder how I could save the selected products' id.
In the console.log(fields, "fields");, this is where I can see the saved product. So how can I save the selected product id as well?
Any help would be appreciated. Thank you.
Codesandbox: https://codesandbox.io/s/react-hook-form-wizard-form-from-reddit-with-data-ouy64e?file=/src/fieldArray.js:322-4143
const products = [
{
prodName: "Tumbler",
price: 1.5,
size: "500",
colorMap: { Black: 20, Pink: 10, Green: 5 },
id: "aRLMZkiSU7T0lcsPCSsV"
},
{
prodName: "Shirt",
price: 2.0,
size: "L",
colorMap: { Blue: 10, Black: 10 },
id: "uTHIR6OQFRuqP9Drft0e"
},
{
size: "200",
price: 2.0,
colorMap: { Green: 50, Red: 19, Black: 20 },
prodName: "Notebook",
id: "y9ECyZBKp2OBekmWym4M"
}
];
const options = products.map(
(object) =>
object.prodName +
" - " +
object.size +
`${object.cat === "CM" || object.cat === "ML" ? "- " + object.cat : ""}` +
" "
);
console.log(options, "options");
const FieldArray = ({ control, register, setValue, getValues }) => {
const { fields, append, remove, prepends } = useFieldArray({
control,
name: "order"
});
console.log(fields, "fields");
renderCount++;
return (
<div>
<ul>
{fields.map((item, index) => {
console.log(item);
return (
<li key={item.id}>
<Controller
control={control}
name={`order.${index}.product`}
render={({ field: { onChange, value = "", ...rest } }) => (
<Autocomplete
{...rest}
onInputChange={(e, newValue) => {
onChange(newValue);
console.log(newValue, "new value");
}}
inputValue={value}
options={products}
// isOptionEqualToValue={(option, value) =>
// option?.value === value?.value
// }
getOptionLabel={(option) =>
option.prodName + " " + option.size
}
// getOptionLabel={(option) => option?.label ?? ""}
renderInput={(params) => (
<TextField
{...params}
label="Product"
variant="outlined"
fullWidth
/>
)}
/>
)}
/>
);
})}
</div>
);
};
export default FieldArray;
Update
this is the submit button in step1.js
const onSubmit = (data) => {
// action(data);
console.log(data, "d");
const newOrder = [];
data.order.forEach(({ product, variation }) => {
const newVariantion = [];
variation.forEach(({ qty, color }) => {
newVariantion.push({ qty: parseInt(qty), color });
});
newOrder.push({ product, variation: newVariantion });
});
actions.updateAction(data);
console.log(newOrder, "new order");
navigate("/step2", newOrder);
};
Update:
How would I be able to push the product ID inside the newOrder where it matches the productID of the selected product?
Some development on answer from this question:
You can always add useState with a first product (save entire product, not just an id) and then manage everything through onChange:
import {useState} from 'react';
/*...something here...*/
const FieldArray = ({ control, register, setValue, getValues }) => {
const [prod, setProd] = useState({0: product[0]});
/*...something here...*/
{fields.map((item, index) => {
/*...something here...*/
<Autocomplete
onChange={(e, v)=>{console.log(v); setProd({...prod, [index]:v});}}
value={prod[index] || {}}
options={products}
/*...other stuff here...*/
Have a look at what is available in console.log(v) inside onChange.
Also check out difference between inputValue and value here.
Update
If you need multiple products to be saved - prod must be an object with key to represent "fields" item. For example, something like this {0: prod1, 1: prod3, 2: prod11}. Then for value use prod[index] and change setter appropriately. (I've edited code above for this case). There is multiple ways to do this - that's just one from top of my head.
Update 2:
I don't know what you want in onSubmit exactly, so here is an idea and you change it to what you want.
In Step1.onSubmit you can do something like that:
// forEach 2nd argument is an index (counter)
data.order.forEach(({ product, variation }, indx) => {
// some code
newOrder.push({ product, variation: newVariantion, prod: prod[indx] });
// more code
}

Re-rendering throws off my logic when using react-dnd

Right now I'm building a DnD game.
It looks like this:
When I drop an answer over a slot, the answer should be in that exact place, so the order matters.
My approach kind of works, but not perfectly.
So, in the parent component I have two arrays which store the slots' content and the answers' content.
The problem is easy to understand, you don't have to look at the code in-depth, just mostly follow my text :)
import DraggableGameAnswers from './DraggableGameAnswers';
import DraggableGameSlots from './DraggableGameSlots';
import { DndProvider } from 'react-dnd';
import { HTML5Backend } from 'react-dnd-html5-backend';
import { useState } from 'react';
type DraggableGameStartProps = {
gameImages: Array<string>,
gameAnswers: Array<string>,
numberOfGameAnswers?: number,
typeOfAnswers: string,
correctAnswer: string
}
function DraggableGameStart(props: DraggableGameStartProps) {
const [slots, setSlots] = useState<string[]>(["", "", "", ""]);
const [answers, setAnswers] = useState<string[]>(props.gameAnswers);
return (
<DndProvider backend={HTML5Backend}>
<div className="draggable-game-content">
<div className="draggable-game">
<DraggableGameSlots
answers={answers}
slots={slots}
setAnswers={setAnswers}
setSlots={setSlots}
numberOfAnswers={4}
slotFor={props.typeOfAnswers}
/>
<DraggableGameAnswers
gameAnswers={answers}
numberOfGameAnswers={props.numberOfGameAnswers}
typeOfAnswers={props.typeOfAnswers}
/>
</div>
</div>
</DndProvider>
);
}
export default DraggableGameStart;
DraggableGameSlots is then a container, which loops through props.slots and renders each slot.
import React, { useState } from "react";
import DraggableGameSlot from "./DraggableGameSlot";
type DraggableGameSlotsProps = {
answers: string[],
slots: string[],
setAnswers: any,
setSlots: any,
numberOfAnswers: number,
slotFor: string
}
function DraggableGameSlots(props: DraggableGameSlotsProps) {
return (
<div className="draggable-game-slots">
{
props.slots.map((val, index) => (
<DraggableGameSlot
index={index}
typeOf={props.slotFor === "text" ? "text" : "image"}
key={index}
answers={props.answers}
slots={props.slots}
setAnswers={props.setAnswers}
setSlots={props.setSlots}
toDisplay={val === "" ? "Drop here" : val}
/>
))
}
</div>
);
}
export default DraggableGameSlots;
In DraggableGameSlot I make my slots a drop target. When I drop an element, I iterate through the slots, and when I reach that element's position in the array, I modify it with the answer.
Until now everything works fine, as expected, I just wrote this for context.
E.g. if the drop target's index is 2, I modify the 3rd position in the slots array. (slots2 for 0-index based)
import { useEffect } from 'react';
import { useDrop } from 'react-dnd';
import './css/DraggableGameSlot.css';
import DraggableGameImage from './DraggableGameImage';
type DraggableGameSlotProps = {
index: number,
typeOf: string,
answers: string[],
slots: string[],
setAnswers: any,
setSlots: any,
toDisplay: string
}
function DraggableGameSlot(props: DraggableGameSlotProps) {
const [{ isOver }, drop] = useDrop(() => ({
accept: "image",
drop(item: { id: string, toDisplay: string }) {
props.setAnswers(props.answers.filter((val, index) => index !== parseInt(item.id)));
props.setSlots(props.slots.map((val, index) => {
if (props.index === index) {
console.log("ITEM " + item.toDisplay);
return item.toDisplay;
}
return val;
}));
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
})
}), [props.slots])
// useEffect(() => console.log("answers " +props.answers), [props.answers]);
// useEffect(() => console.log("slots " + props.slots), [props.slots]);
const dragClass: string = isOver ? "is-dragged-on" : "";
return (
<>
{(props.typeOf === "image" && props.toDisplay !== "Drop here") ?
<>
<span>da</span>
<DraggableGameImage
className={`game-answers-image ${dragClass}`}
imgSrc={props.toDisplay}
imgAlt={"test"}
dndRef={drop}
/>
</>
:
<div className={`draggable-game-slot draggable-game-slot-for-${props.typeOf} ${dragClass}`} ref={drop}>
<span>{props.toDisplay}</span>
</div>
}
</>
)
}
export default DraggableGameSlot;
The problem comes with the logic in DraggableGameAnswer.
First, the container, DraggableGameAnswer. Using the loop, I pass that "type" (it is important because from there the error happens)
DraggableGameAnswers.tsx
import './css/DraggableGameAnswers.css';
import GameNext from '../GameComponents/GameNext';
import DraggableGameAnswer from './DraggableGameAnswer';
import { useEffect } from 'react';
type DraggableGameAnswersProps = {
gameAnswers: Array<string>,
numberOfGameAnswers?: number,
typeOfAnswers: string
}
function DraggableGameAnswers(props: DraggableGameAnswersProps) {
let toDisplay: Array<string>;
if(props.numberOfGameAnswers !== undefined)
{
toDisplay = props.gameAnswers.slice(props.numberOfGameAnswers);
}
else
{
toDisplay = props.gameAnswers;
}
useEffect(() => console.log(toDisplay));
return (
<div className="draggable-game-answers">
{
toDisplay.map((val, index) => (
<DraggableGameAnswer
key={index}
answer={val}
typeOfAnswer={props.typeOfAnswers}
type={`answer${index}`}
/>
))}
<GameNext className="draggable-game-verify" buttonText="Verify"/>
</div>
);
}
export default DraggableGameAnswers;
In DraggableGameAnswer, I render the draggable answer:
import DraggableGameImage from "./DraggableGameImage";
import "./css/DraggableGameAnswer.css";
import { useDrag } from "react-dnd";
import { ItemTypes } from "./Constants";
type DraggableGameAnswerProps = {
answer: string,
typeOfAnswer: string,
type: string
}
function DraggableGameAnswer(props: DraggableGameAnswerProps) {
const [{ isDragging }, drag] = useDrag(() => ({
type: "image",
item: { id: ItemTypes[props.type],
toDisplay: props.answer,
typeOfAnswer: props.typeOfAnswer },
collect: (monitor) => ({
isDragging: !!monitor.isDragging(),
})
}))
return (
<>
{props.typeOfAnswer === "image" ?
<DraggableGameImage
className="draggable-game-image-answer"
imgSrc={props.answer}
imgAlt="test"
dndRef={drag}
/> :
<div className="draggable-game-text-answer" ref={drag}>
{props.answer}
</div>
}
</>
);
}
export default DraggableGameAnswer;
Constants.tsx (for ItemTypes)
export const ItemTypes: Record<string, any> = {
answer0: '0',
answer1: '1',
answer2: '2',
answer3: '3'
}
Okay, now let me explain what's happening.
The error starts because of the logic in DraggableGameAnswers container - I set the type using the index for the loop.
Everything works fine at the beginning. Then, let's say, I move answer3 in in the 4th box. It looks like this now:
It is working fine until now.
But now, the components have re-rendered, the map runs again, but the array is shorter with 1 and answer1's type will be answer1, answer2's type will be answer2, and answer4's type will be answer3.
So, when I drag answer4 over any box, I will get another answer3, like this:
How can I avoid this behavior and still get answer4 in this situation? I'm looking for a react-style way, better than my idea that I'll describe below.
So, here is how I delete an answer from the answers array:
props.setAnswers(props.answers.filter((val, index) => index !== parseInt(item.id)));
I can just set the deleted's answer position to NULL, to keep my array length for that index, and when I render the answers if the value is NULL, just skip it.

Issue adding value to an array of object via dynamic input field

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)

Categories