How to pass initial values to Field Array and handle form values? - javascript

I have react select using sortable container the problem I am having is that the values that are extracted is like this
{
"fruits": [
{
"fruitName": {
"id": 3,
"value": "vanilla",
"label": "Vanilla"
}
},
{
"fruitName": {
"id": 1,
"value": "chocolate",
"label": "Chocolate"
}
}
]
}
if you notice that fruitName is duplicated each time I select an option despite that I don't need it I just want it like a list like this
{
"fruits": [
{
"id": 3,
"value": "vanilla",
"label": "Vanilla"
},
{
"id": 1,
"value": "chocolate",
"label": "Chocolate"
}
]
}
and if I remove fruitName field from field name it doesn't work correctly also how to pass initial values to this if I already have selected list values of fruits
import React from "react";
import Styles from "./Styles";
// import { render } from "react-dom";
import { Form, Field } from "react-final-form";
import arrayMutators from "final-form-arrays";
import { FieldArray } from "react-final-form-arrays";
import {
SortableContainer,
SortableElement,
SortableHandle,
} from "react-sortable-hoc";
import Select from "react-select";
const options = [
{ id: 1, value: "chocolate", label: "Chocolate" },
{ id:2, value: "strawberry", label: "Strawberry" },
{ id:3, value: "vanilla", label: "Vanilla" },
];
const DragHandle = SortableHandle(() => (
<span style={{ cursor: "move" }}>Drag</span>
));
const SortableItem = SortableElement(({ name, fields, value }) => (
<li>
<DragHandle />
<Field name={`${name}.fruittName`}>
{({ input }) => (
<Select options={options} placeholder="Select Location" {...input} />
)}
</Field>
<span onClick={() => fields.remove(value)} style={{ cursor: "pointer" }}>
Remove
</span>
</li>
));
const SortableList = SortableContainer(({ items }) => {
return (
<ul>
{items.map((name, index) => (
<SortableItem
key={`item-${index}`}
index={index}
value={index}
name={name}
fields={items}
/>
))}
</ul>
);
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const onSubmit = async (values) => {
await sleep(300);
window.alert(JSON.stringify(values, 0, 2));
};
const sortEnd =
(move) =>
({ oldIndex, newIndex }) => {
move(oldIndex, newIndex);
};
const Home = () => {
return (
<div>
<Styles>
<h1>React Final Form - Array Fields</h1>
<a href="https://github.com/erikras/react-final-form#-react-final-form">
Read Docs
</a>
<Form
onSubmit={onSubmit}
mutators={{
...arrayMutators,
}}
render={({
handleSubmit,
form: {
mutators: { push, pop },
},
pristine,
reset,
submitting,
values,
}) => {
return (
<form onSubmit={handleSubmit}>
<div>
<label>Company</label>
<Field name="company" component="input" />
</div>
<div className="buttons">
<button
type="button"
onClick={() => push("fruits", undefined)}
>
Add Customer
</button>
<button type="button" onClick={() => pop("fruits")}>
Remove Customer
</button>
</div>
<FieldArray name="fruits">
{({ fields }) => (
<SortableList
useDragHandle={true}
items={fields}
onSortEnd={sortEnd(fields.move)}
/>
)}
</FieldArray>
<div className="buttons">
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={submitting || pristine}
>
Reset
</button>
</div>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
);
}}
/>
</Styles>
</div>
);
};
export default Home;

This will help your answer,
If you don't want to repeat an object/value you have to just filter out it.
Below is an example,
fruits = [
{id: 1, name:'Mango'},
{id: 2, name:'Apple'},
{id: 3, name:'Orange'}
]
If your selected id is 2, So you want every other value except 2,
let selectedId = 2;
let filteredFruitArray = fruits.filter((fruit) => fruit.id !== selectedId);
console.log(filteredFruitArray); //{id: 1, name:'Mango'}{id: 3, name:'Orange'}

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

How to get selected values from multiple dropdowns on button click using ReactJs

I want to console.log() multiple values from five different dropdowns on a button click. I have done this for one dropdown, but I don't know how to do it for more. I'm still a beginner.
Here's my code:
export default function Suma() {
const typedemande = [
{ value: "first", label: "first" },
{ value: "second", label: "second" },
];
const [message, setMessage] = useState('');
const handleChange = event => {
setMessage(event);
};
const handleClick = event => {
event.preventDefault();
console.log(message);
};
return (
<div>
<div className="col-lg">
<Select placeholder="choose" id="message" className="react-dropdown " name="message" onChange={handleChange}
value={message}
isClearable
isSearchable={false}
classNamePrefix="dropdown"
options={typedemande}
/>
</div>
<div className="text-center">
<button className="mr-2 btn btn-primary" onClick={handleClick}>Click me</button>
</div>
</div>
);
};
I hope you are looking for this one:
export default function App() {
const typedemande = [
{ value: "first", label: "first" },
{ value: "second", label: "second" },
{ value: "third", label: "third" },
{ value: "fourth", label: "fourth" },
{ value: "five", label: "five" },
];
const [showAll, setShowAll ] = useState([]);
const [dropdowns,setDrodowns] = useState({
'message1': '',
'message2': '',
'message3': '',
'message4': '',
'message5': '',
});
const handleChange = (event) => {
setDrodowns({...dropdowns,[event.target.name]:event.target.value});
}
const handleClick = (event) => {
event.preventDefault(); // if you use the element inside `form` then it would prevent to submit
console.log(dropdowns);//to log the values in console
setShowAll(Object.values(dropdowns));// to show the changes in UI
}
return (
<div>
<div className="col-lg">
<Select
name="message1"
onChange={handleChange}
value={"second"}
options={typedemande}
/>
<Select
name="message2"
onChange={handleChange}
value={"second"}
options={typedemande}
/>
<Select
name="message3"
onChange={handleChange}
value={"second"}
options={typedemande}
/>
<Select
name="message4"
onChange={handleChange}
value={"second"}
options={typedemande}
/>
<Select
name="message5"
onChange={handleChange}
value={"second"}
options={typedemande}
/>
</div>
<hr/>
<ul>
{ showAll.map((val,i)=><li key={i}>{i+1} --- {val}</li>) }
</ul>
<hr/>
<div className="text-center">
<button className="mr-2 btn btn-primary" onClick={handleClick}>Click me</button>
</div>
</div>
);
}
For details check the code sandbox link
Out put
Edit: Based on user comments I edited the answer
You could pass a parameter to your handleChange.
const handleChange = (event, position) => {
console.log(position);
};
<Select onChange={(e) => handleChange(e, 1)} />
<Select onChange={(e) => handleChange(e, 2)} />
<Select onChange={(e) => handleChange(e, 3)} />
Improving axtck's answer, you can get each select value like below
import React, {useState} from 'react';
import Select from 'react-select';
export function App(props) {
const typedemande = [
{ value: "first", label: "first" },
{ value: "second", label: "second" },
];
const [messages, setMessages] = useState([]);
const handleChange = (event, pos) => {
console.log(pos)
console.log(event.value)
let mz = [...messages];
if (mz.length > 0 && mz.findIndex(msg => msg.index == pos) > -1) {
mz[mz.findIndex(msg => msg.index == pos)] = event.value;
setMessages(mz);
}
else {
mz.push({
index: pos,
value: event.value
});
setMessages(mz);
}
};
const handleClick = event => {
event.preventDefault();
for (let i = 0; i < messages.length; i++)
console.log(messages[i].value)
};
return (
<div>
<div className="col-lg">
<Select placeholder="choose" id="message" className="react-dropdown " name="message" onChange={(e) => handleChange(e, 1)}
value={messages[0] ? messages[0].label : ''}
isClearable
isSearchable={false}
classNamePrefix="dropdown"
options={typedemande}
/>
<Select placeholder="choose" id="message" className="react-dropdown " name="message" onChange={(e) => handleChange(e, 2)}
value={messages[1] ? messages[1].label : ''}
isClearable
isSearchable={false}
classNamePrefix="dropdown"
options={typedemande}
/>
</div>
<div className="text-center">
<button className="mr-2 btn btn-primary" onClick={handleClick}>Click me</button>
</div>
</div>
);
}

Unable to successfully loop through a redux store after a dispatch function

I'm trying to add product items to my redux store called cart. After adding an item I then compare both stores product(redux store) and cart(redux store) to check if the product has the same itemCode(item code. if they do I would like to Hide the add button and show the remove button. Unfortunately I'm getting different results, please look at the picture below for reference:
interface IProps {
items: ItemInterface[];
documentItems: ItemInterface[];
onAddItem: any;
}
const ItemFlatList2: FC<Partial<IProps>> = ({
items,
documentItems,
onAddItem,
}) => {
return (
<div className={styles.container}>
<ul>
{items!.map((item) => {
return (
<div className={styles.itemContainer}>
<div key={item.itemCode}>
<li>{item.itemCode}</li>
<li>{item.itemDescription}</li>
{documentItems!.length === 0 ? (
<AddButton
title={"ADD"}
onClick={() =>
onAddItem(
item.itemCode,
item.itemDescription,
item.itemSellingPrice
)
}
/>
) : (
documentItems!.map((documentItem) => {
if (documentItem.itemCode === item.itemCode) {
return <RedButton title={"Remove"} />;
}
if (documentItem.itemCode !== item.itemCode) {
return (
<AddButton
title={"ADD"}
onClick={() =>
onAddItem(
item.itemCode,
item.itemDescription,
item.itemSellingPrice
)
}
/>
);
}
})
)}
</div>
<div>
<li>Hello world</li>
</div>
</div>
);
})}
</ul>
</div>
);
};
export default ItemFlatList2;
The Cart Store:
const initialState: Partial<DocumentDetailsInterface>[] = [
];
const cartStore = createSlice({
name: "quotation reducer",
initialState,
reducers: {
add: {
reducer: (
state,
{ payload }: PayloadAction<DocumentDetailsInterface>
) => {
state.push(payload);
},
prepare: (item) => ({
payload: item,
}),
},
edit: (state, { payload }) => {},
remove: (
state,
{ payload }: Partial<PayloadAction<DocumentDetailsInterface>>
) => {
const findItem = state.findIndex((item) => payload!.code === item.code);
if (findItem !== 1) {
state.splice(findItem, 1);
}
},
},
extraReducers: {},
});
and The Product Store:
const initialState: ItemInterface[] = [
{
_id: "sdfsd",
itemType: "Physical Item",
itemUnitOfMeasure: "Unit",
itemCode: "PPC10",
itemDescription: "PPC Cement",
itemCostPrice: 50,
itemSellingPrice: 80,
itemQuantity: 100,
vatStatus: "Standard rate 15%",
},
{
_id: "qew",
itemType: "Physical Item",
itemUnitOfMeasure: "Unit",
itemCode: "2",
itemDescription: "Sepako Cement",
itemCostPrice: 30,
itemSellingPrice: 60,
itemQuantity: 100,
vatStatus: "Standard rate 15%",
},
{
_id: "sdfsd",
itemType: "Physical Item",
itemUnitOfMeasure: "Unit",
itemCode: "1",
itemDescription: "PPC Cement",
itemCostPrice: 50,
itemSellingPrice: 80,
itemQuantity: 100,
vatStatus: "Standard rate 15%",
},
];
const itemSlice = createSlice({
name: "item reducer",
initialState,
reducers: {},
extraReducers: {},
});
It looks like you have wrong logic in you render method. You displayed "Add" button when there are no items in documentItems and the if any items inside you keep adding "Add" buttons if they are not equal to itemCode. So basically you have 2 loops. First is render items, and second one is to render buttons for each item. But you can use one loop to render items and have logic to check if that item is already in the documentItems array - if not then display "Add" button, else "Remove" button.
return (
<div className={styles.container}>
<ul>
{items!.map(item => {
return (
<div className={styles.itemContainer} key={item.itemCode}>
<div key={item.itemCode}>
<li>{item.itemCode}</li>
<li>{item.itemDescription}</li>
{documentItems!.findIndex(
documentItem => documentItem.itemCode === item.itemCode,
) === -1 ? (
<AddButton
title={'ADD'}
onClick={() =>
onAddItem(
item.itemCode,
item.itemDescription,
item.itemSellingPrice,
)
}
/>
) : (
<RedButton title={"Remove"} />
)}
</div>
<div>
<li>Hello world</li>
</div>
</div>
);
})}
</ul>
</div>
);
I think you need to provide a unique key to button. Write the statemen when you are changing the state
<AddButton
title={"ADD"}
key={item.itemCode}
onClick={() =>
onAddItem(
item.itemCode,
item.itemDescription,
item.itemSellingPrice
)
}
/>
You used map function to check if item exists in the documentItems. I think changing it to some function will work out.
documentItems!.some((documentItem) => {
return documentItem.itemCode === item.itemCode
}
) ? (<RedButton title={"Remove"} />): (
<AddButton
title={"ADD"}
onClick={() =>
onAddItem(
item.itemCode,
item.itemDescription,
item.itemSellingPrice
)
}
/>
);

How change the format of checkbox in react final form

I implemented the form through react final form
const products= [
{ label: "T Shirt", value: "tshirt" },
{ label: "White Mug", value: "cup" },
{ label: "G-Shock", value: "watch" },
{ label: "Hawaiian Shorts", value: "shorts" },
];
<>
<Form
onSubmit={onSubmit}
render={({ handleSubmit, pristine, invalid, values }) => (
<form onSubmit={handleSubmit} className="p-5">
{products &&
products.map((product, idx) => (
<div className="custom-control custom-checkbox" key={idx}>
<Field
name="state"
component="input"
type="checkbox"
value={product.value}
/>
<label
className="custom-control-label"
htmlFor={`customCheck1-${product.value}`}
>
{product.label}
</label>
</div>
))}
<button type="submit" disabled={pristine || invalid}>
Submit
</button>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
/>
</>
If I am selecting checkboxes the checked values are showing array of values like [tshirt,cup] but I need to show the array of objects like [ { label: "T Shirt", value: "tshirt" }, { label: "White Mug", value: "cup" }]
I tried so many ways but I have not any luck. Please help me to out of this problem
values will always be the array consisting of the "value" attribute for the Field tag.
If you want the object from the products array,you could do the following
console.log(values.map(val => products.find(p => p.value === val)))
or create an object first via reduce & then use it.
const obj =products.reduce((map,p)=>{
map[value]=p
return map
},{})
console.log(values.map(v => productMap[v]))
add a onchange method to you input. the method must take value of product.
const [selectedProducts, setSelectedProducts] = useState([]);
const handleChange = (value) =>{
const itemToAdd = products.find(product => product.value === value);
const index = selectedProducts.findIndex(item => item.value === value);
if (index === -1){
setSelectedProducts([...selectedProducts, products[index]])
}else {
const data = [...selectedProducts];
data.splice(index, 1);
setSelectedProducts(data);
}
}
some change to jsx
<Field
onChange = {handleChange}
name="state"
component="input"
type="checkbox"
value={product.value}
checked = {selectedProducts.findIndex(item => item.value === value)!== -1}
/>

How can I check only one check box at a time instead of all in a list of checkboxes in React

I have a list of chat room channels for people to talk i.e there is a lifestyle channel, shopping channel, pets channel etc.
I am now trying to categorise each channel to make it easier for the user to find what they want. In order to do so, on creation of a chatroom channel I need the user to select which category the channel they are creating best fits into. A bit like YouTube does when you upload a video.
So far I have created a separate component which is a list of checkboxes with the different categories the user can put their channel into:
import React from 'react';
const options = [
{ label: "Lifestyle", value: "lifestyle"},
{ label: "Area", value: "area" },
{ label: "Random", value: "random" },
{ label: "Comedy", value: "comedy" },
{ label: "Entertainment", value: "entertainment" }
];
const ChannelCategory = (props) => {
return (
<div>
{props.title}
<ul>
{options.map((option) => (
<li key={props.key}>
<label>
{option.label}
<input
className={props.className}
name="test"
checked={props.checked}
onChange={() => props.onChange(option.value)}
type="checkbox"
/>
</label>
</li>
))}
</ul>
</div>
)
};
export default ChannelCategory;
I am using the above component on the page below, I would like that when the user selects just ONE of the options only ONE input box is checked, however at the moment when I click ONE input box for instance lifestyle they ALLLL get checked and for every single channel too:( Any ideas why?
const [checked, setCheckBoxChecked] = useState(false);
[...]
const onAddCategory = (value) => {
console.log(value);
if (value === "lifestyle") {
setCheckBoxChecked(checked => !checked);
}
if (value === "area") {
setCheckBoxChecked(checked => !checked);
}
if (value === "random") {
setCheckBoxChecked(checked => !checked);
}
if (value === "comedy") {
setCheckBoxChecked(checked => !checked);
}
};
[...]
const options = [
{ label: "Lifestyle", value: "lifestyle"},
{ label: "Area", value: "area" },
{ label: "Random", value: "random" },
{ label: "Comedy", value: "comedy" },
{ label: "Entertainment", value: "entertainment" }
];
return (
<form noValidate autoComplete='off' onSubmit={onSubmit}>
<Card style={styles.card}>
<CardContent>
<Box padding={3}>
<FormLegend title={`${formTitle} (${channels.length})`} description={formDescription} />
<Box marginTop={3} width='50%'>
<Grid container direction='column' justify='flex-start' alignItems='stretch' spacing={1}>
{channels.map(channel => {
return (
<Grid key={channel.key} item style={styles.gridItem} justify="space-between">
<ChannelListItem
channel={channel}
isSaving={isSaving}
onDeleteChannelClick={onDeleteChannelClick}
key={channel.Key}
onFormControlChange={onFormControlChange}
onUndoChannelClick={onUndoChannelClick}
/>
<ChannelCategory
key={channel.key}
options={options}
onChange={value => onAddCategory(value)}
title="Add your chatroom to a category so that users can find it easily"
checked={checked}
/>
</Grid>
)
})}
[...]
</Grid>
</Grid>
</Box>
</Box>
</CardContent>
</Card>
</form>
);
Instead of storing true or false inside the checked variable, you should store the value inside of checked. Like this:
const onChangeAttribute = (value) => {
console.log(value);
setCheckBoxChecked(value);
};
And now while rendering the checkbox you should check if checked is equal to the name of that checkbox like this:
<input
className={props.className}
name={option.value}
checked={props.checked === option.value}
onChange={() => props.onChange(option.value)}
type="checkbox"
/>
This should resolve your issue.
Use an array to store all checked boxes and in your ChannelCategory check if the current value exists in the checked array then set checked to true for that checkbox. If you want to select only one category use radio buttons
const {useState, useEffect} = React;
const options = [
{ label: "Lifestyle", value: "lifestyle" },
{ label: "Area", value: "area" },
{ label: "Random", value: "random" },
{ label: "Comedy", value: "comedy" },
{ label: "Entertainment", value: "entertainment" }
];
const ChannelCategory = props => {
return (
<div>
{props.title}
<ul>
{props.options.map(option => (
<li key={props.key}>
<label>
{option.label}
<input
className={props.className}
name={option.value}
checked={props.checked.includes(option.value)}
onChange={e => props.onChange(e.target.checked, option.value)}
type="checkbox"
/>
</label>
</li>
))}
</ul>
</div>
);
};
function App() {
const [checked, setCheckBoxChecked] = useState([]);
const onAddCategory = (isChecked, value) => {
const temp = [...checked];
if (isChecked) {
temp.push(value);
setCheckBoxChecked(temp);
return;
}
setCheckBoxChecked(temp.filter(item => item !== value));
};
return (
<div className="App">
<ChannelCategory
key={"channel.key"}
options={options}
onChange={onAddCategory}
title="Add your chatroom to a category so that users can find it easily"
checked={checked}
/>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("react")
);
<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="react"></div>
Radio buttons example

Categories