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}
/>
Related
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'}
I have a component renderRoyaltyAccount, that gets rendered x number of times depending on the input that sets royaltyAccount.
In this component I have 2 fields, one for the name of the account, and the second a percentage.
What I wanted to do is depending of the number of accounts to create, create an object with those two fields for each, example :
If he chooses to create two accounts , to have a the end (what I thought but could be not the best choice :) ) :
{
1: {
"account": "test1",
"percentage": 2,
},
2: {
"account": "test#",
"percentage": 0.5
}
}
I tried with a useState and updating it with onChange with inputs, but it was a mess LOL.
If anyone could help me with this state, and specially the logic with objects and hooks. Thank you
export default function FormApp() {
const [royaltyAccount, setRoyaltyAccount] = useState(1);
const [allAccounts, setAllAccounts] = useState ({
{
"account": "",
"percentage": 1,
},
})
const renderRoyaltyAccounts = () => {
let items = [];
for (let i = 0; i < royaltyAccount; i++) {
items.push(
<div key={i}>
<div>
<label>Royalty Account n° {i + 1}</label>
<input onChange={()=> setAllAccounts(???)} type="text"/>
</div>
<div>
<label>Royalty %</label>
<input onChange={()=> setAllAccounts(???)} type="text"/>
</div>
</div>
)
}
return items;
}
return (
<>
<label> Royalty account(s)</label>
<input onChange={(e) => { setRoyaltyAccount(e.target.value)}} type="number"/>
{
renderRoyaltyAccounts()
}
</>
)
}
Dynamically compute the allAccounts state array from the initial royaltyAccount state value. Add an id property to act as a GUID for each account object.
Create a handleRoyaltyAccountChange onChange handler to either append a computed diff of the current allAccounts array length to the new count value, or to slice up to the new count if less.
Create a handleAccountUpdate onChange handler to shallow copy the allAccounts state array and update the specifically matching account object by id.
Give the inputs a name attributeand pass the mappedallAccountselement object's property as thevalue` prop.
Code:
import { useState } from "react";
import { nanoid } from "nanoid";
function FormApp() {
const [royaltyAccount, setRoyaltyAccount] = useState(1);
const [allAccounts, setAllAccounts] = useState(
Array.from({ length: royaltyAccount }).map(() => ({
id: nanoid(),
account: "",
percentage: 1
}))
);
const handleRoyaltyAccountChange = (e) => {
const { value } = e.target;
const newCount = Number(value);
setRoyaltyAccount(newCount);
setAllAccounts((accounts) => {
if (newCount > accounts.length) {
return accounts.concat(
...Array.from({ length: newCount - accounts.length }).map(() => ({
id: nanoid(),
account: "",
percentage: 1
}))
);
} else {
return accounts.slice(0, newCount);
}
});
};
const handleAccountUpdate = (id) => (e) => {
const { name, value } = e.target;
setAllAccounts((accounts) =>
accounts.map((account) =>
account.id === id
? {
...account,
[name]: value
}
: account
)
);
};
return (
<>
<label> Royalty account(s)</label>
<input
type="number"
onChange={handleRoyaltyAccountChange}
value={royaltyAccount}
/>
<hr />
{allAccounts.map((account, i) => (
<div key={account.id}>
<div>
<div>Account: {account.id}</div>
<label>
Royalty Account n° {i + 1}
<input
type="text"
name="account"
onChange={handleAccountUpdate(account.id)}
value={account.account}
/>
</label>
</div>
<div>
<label>
Royalty %
<input
type="text"
name="percentage"
onChange={handleAccountUpdate(account.id)}
value={account.percentage}
/>
</label>
</div>
</div>
))}
</>
);
}
I've created script for dynamic form, but there's 2 things which I can't get and my head is exploding right now, hopefully somebody would help me with that.
After creating new fields - I can't remove fields depends on button which was clicked.
And after removing some of those fields, I have this error with fieldsenter image description here
import React from "react";
import {useState , useEffect} from "react";
import ReactDOM from "react-dom";
import "./index.css";
const Form = () =>{
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([{
id : 1,
name: "",
phone: "",
age: ""
}])
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{
fields.map((elem, index) => {
return(
<FormElement {...elem} fields={fields} fieldsChanger={fieldsChanger} fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger}/>
)
})
}
<AddMore fieldsLength={fieldsLength} fieldsLengthChanger={fieldsLengthChanger} fields={fields} fieldsChanger={fieldsChanger}/>
</div>
</>
)
}
const FormElement = ({fieldsLength ,...props}) =>{
function inputHandler(e, id){
console.log(e.target.name);
const values = [...props.fields];
values[id-1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e,id){
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function(elem,index){
console.log("index:" , index, "ID : ", id);
if (index + 1 != id){
return elem;
}
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([])
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input type="text" value={props.fields.name} name="name" onChange={e => inputHandler(e , props.id)} />
</div>
<div className="form__element">
<input type="text" value={props.fields.phone} name="phone" onChange={e => inputHandler(e , props.id)}/>
</div>
<div className="form__element">
<input type="text" value={props.fields.age} name="age" onChange={e => inputHandler(e , props.id)}/>
</div>
{
fieldsLength > 1 ? <div className="remove__field">
<a href="#" onClick={e=>removeElement(e , props.id)}>Remove</a>
</div> : ""
}
</div>
)
}
const AddMore = (props) =>{
function addMore(){
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([...props.fields, {id:props.fields.length + 1 , name: "" , phone : "" , age :''} ]);
}
return (
<div className="add__more">
<a href="#" onClick={e=> addMore()}>Add element</a>
</div>
)
}
ReactDOM.render(<Form/> , document.getElementById("root"));
Where I'm wrong - would be really helpfull to understand what is the problem
Always use key when rendering list.
<FormElement key={elem.id} {...elem} ...
https://reactjs.org/docs/lists-and-keys.html#keys
1. You should use the props.id to remove a form instead of using index.
e.g. this must be a bug.
if (index + 1 != id) {
return elem;
}
2. And you should use id by the unique value generation instead of using array's length.
3. You should provide the key prop in the render of the form fields. Otherwise, React can't distinguish forms in rendering.
And the key should be unique whenever you add or remove items.
e.g. See the updated code. You need to use elem.id. (Of course, you should generate id in the add method.
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
4. You could basically use a timestamp. Please use your own generation logic in the production. You can use uuid generation package.
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
My advices:
You don't need to use this state variable const [fieldsLength, fieldsLengthChanger] = useState(1);
Because we can get this value by fields.length.
There are a few problems in terms of components design. e.g. Please try to define addMore() in form then you don't need to pass fields, fieldsChanger as props.
Here is a full working code with your old code commented.
const Form = () => {
const [fieldsLength, fieldsLengthChanger] = useState(1);
const [fields, fieldsChanger] = useState([
{
id: new Date().getTime(),
name: "",
phone: "",
age: ""
}
]);
return (
<>
<div className="form__wrapper">
<h2>Form </h2>
{fields.map((elem, index) => {
return (
<FormElement
key={elem.id}
{...elem}
fields={fields}
fieldsChanger={fieldsChanger}
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
/>
);
})}
<AddMore
fieldsLength={fieldsLength}
fieldsLengthChanger={fieldsLengthChanger}
fields={fields}
fieldsChanger={fieldsChanger}
/>
</div>
</>
);
};
const FormElement = ({ fieldsLength, ...props }) => {
function inputHandler(e, id) {
console.log(e.target.name);
const values = [...props.fields];
const item = values.find((x) => x.id === props.id);
if (item) {
item[e.target.name] = e.target.value;
}
// values[id - 1][e.target.name] = e.target.value;
props.fieldsChanger(values);
}
function removeElement(e, id) {
e.preventDefault();
var arr = [...props.fields];
const newArray = arr.filter(function (elem, index) {
/*console.log("index:", index, "ID : ", id);
if (index + 1 != id) {
return elem;
}*/
return elem.id !== id;
});
// console.log(newArray);
// arr.splice(3, 1);
props.fieldsChanger([]);
props.fieldsChanger(newArray);
}
return (
<div className="group__form">
<div className="form__element">
<input
type="text"
value={props.fields.name}
name="name"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.phone}
name="phone"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
<div className="form__element">
<input
type="text"
value={props.fields.age}
name="age"
onChange={(e) => inputHandler(e, props.id)}
/>
</div>
{fieldsLength > 1 ? (
<div className="remove__field">
<a href="#" onClick={(e) => removeElement(e, props.id)}>
Remove
</a>
</div>
) : (
""
)}
</div>
);
};
const AddMore = (props) => {
function addMore() {
props.fieldsLengthChanger(props.fields.length + 1);
props.fieldsChanger([
...props.fields,
//{ id: props.fields.length + 1, name: "", phone: "", age: "" }
{ id: new Date().getTime(), name: "", phone: "", age: "" }
]);
}
return (
<div className="add__more">
<a href="#" onClick={(e) => addMore()}>
Add element
</a>
</div>
);
};
I have this component:
import React from 'react';
const options = [
{ label: "Lifestyle", value: "lifestyle"},
{ label: "Area", value: "area" },
{ label: "Random", value: "random" }
];
const ChannelCategory = props =>
props.visible ? (
<div>
{props.title}
<ul>
{options.map((option) => (
<li key={option.value}>
<label>
{option.label}
<input
className={props.className}
name={props.name} // need to be different
selected={props.selected === option.value} // e.g. lifestyle === lifestyle
onChange={() => props.onChange(option.value)}
type="radio"
/>
</label>
</li>
))}
</ul>
</div>
) : null;
export default ChannelCategory;
I am rendering it on another page here in a .map:
let displayExistingChannels = null;
if (channels !== null){
displayExistingChannels = (
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}
/>
{channel.category}
<ChannelCategory
visible={true}
onChange={value => setCategoryName(value)}
title="Edit Category"
selected={channel.category}
name={channel.key} // unique for every channel
/>
</Grid>
)
})
)
}
I am using fake data for the map:
const fakeChannelData = setupChannels(
[{id: "2f469", name: "shopping ", readOnly: false, category: "lifestyle"},
{id: "bae96", name: "public", readOnly: true, category: "null"},
{id: "06ea6", name: "swimming ", readOnly: false, category: "sport"},
{id: "7e2bb", name: "comedy shows ", readOnly: false, category: "entertainment"}]);
const [channels, setChannels] = useState(fakeChannelData);
Please can someone tell me why when I add selected={channel.category} in my .map function it does not show the selected category preselected on the FE on page load? Not sure where I have gone wrong? Thanks!
checked is the correct attribute to use for input tag, not selected.
<input
...
checked={props.selected === option.value}
...
/>
ref: https://developer.mozilla.org/fr/docs/Web/HTML/Element/Input/radio
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