How to add form fields dynamically in react js - reactjs

I want to add form fields dynamically.
I also want that on click the durationprice add dynamically and also a button to add dynamically full plan.
{ plan: [ { planname: "", description: "", cuisine: "", durationprice: [{duration: "",maximumduration: "", price: ""}]}]}
import React, {
Component,
useState
} from "react";
import DurationPrice from "./DurationandPrice";
import Rules from "./Rules";
const TOKEN = 'hello';
export function PlanForm() {
const [inputList, setInputList] = useState([{
planName: "",
description: "",
cuisine: "",
}, ]);
const handleChnage = (e, index) => {
const {
name,
value
} = e.target;
const list = [...inputList];
list[index][name] = value;
setInputList(list);
};
const handleInput = () => {
const list = [...inputList];
list.push({
planName: "",
description: "",
cuisine: ""
});
setInputList(list);
// setInputList([...inputList,{firstName:'',lastName:''}]);
};
const handleRemove = (index) => {
const list = [...inputList];
list.splice(index, 1);
setInputList(list);
};
const handleSubmit = (e) => {
e.preventDefault();
};
const handleSave = (e) => {
e.preventDefault();
console.log('button clicked');
fetch(`https://cosynest-api.herokuapp.com/api/plans/create`, {
method: 'POST',
body: JSON.stringify({
'duration': inputList.duration,
'maximumDuration': inputList.maximumDuration,
'price': inputList.price
}),
headers: new Headers({
Authorization: `Bearer ${TOKEN}`,
'content-type': 'application/json'
})
})
.then(response => response.json())
.then(console.log(inputList))
.then(localStorage.setItem('Token-CreateShop', TOKEN))
.catch(console.log('error'))
}
// debugger;
return ( <
div className = "plan-form" > {
inputList.map((item, i) => {
return ( <
form key = {
i
}
onSubmit = {
handleSubmit
} >
<
input type = "text"
name = "planName"
className = "mr-10 input "
placeholder = "Plan Name"
value = {
item.planName
}
onChange = {
(e) => handleChnage(e, i)
}
/> <
input type = "text"
name = "description"
className = "mr-10 input "
placeholder = "Description"
value = {
item.description
}
onChange = {
(e) => handleChnage(e, i)
}
/> <
input type = "cuisine"
onChange = {
(e) => handleChnage(e, i)
}
value = {
item.cuisine
}
className = "mr-10 input "
name = "cuisine"
placeholder = "Cuisine" /
>
<
h2 > Duration and Price < /h2> <
DurationPrice / >
<
br / >
<
div > {
inputList.length - 1 === i && ( <
button type = "button"
onClick = {
handleInput
}
className = "mr-10 btn"
value = "Add Plan" >
Add Plan <
/button>
)
} {
inputList.length !== 1 && ( <
button onClick = {
() => handleRemove(i)
}
className = "mr-10 btn-red"
value = "Remove Plan" >
Remove <
/button>
)
} <
/div>
<
/form>
);
})
} <
button onClick = {
handleSave
}
className = "btn-pink btn "
type = "submit" > Save PLANS < /button> {
/* <pre>
{JSON.stringify(inputList,null,3)}
</pre> */
} <
/div>
);
}
export default PlanForm;

you can use one state that will have all the formData states inside it and for the input elements you can use map to render
lets say you have to create form for these fields
let formElements = [{
label: "Name", // this is label
key: 'name' // this is unique key to be used as state name
}, {
label: "Phone number",
key: 'phone'
}, {
label: "House",
key: 'house'
}, {
label: "City",
key: 'city'
}, {
label: "State",
key: 'state'
}, {
label: "Country",
key: 'country'
}]
now you can create your component from this element something like this
export default function App() {
const [formData, setFormData] = useState({} as any);
const handleChange = (value: string, key: string) => {
setFormData({ ...formData, ...{ [key]: value } });
}
const submit = () => {
alert(JSON.stringify(formData))
}
return (
<form>
Form goes here
{formElements.map(formElement => {
return <div>
{formElement.label}
<input value={formData[formElement.key]}
onChange={(e) => { handleChange(e.target.value, formElement.key) }} />
</div>
})}
<button onClick={(e) => { e.preventDefault(); submit() }}>submit</button>
</form>
);
}
Now whenever you want to update the form fields just update your formElements Array.
in your case you can store formElements in state and update that like this
const [formElement,setFormElements] = useState([]);
useEffect(()=>{
setFormElements(formElement)
},[]);
// update your state with new element like this on button click
const updateFormElememt = (newElement)=>{
setFormElements([...formElements,...newElement])
}

Related

How to update dynamic multiple input (user can add those input themself)?

I have a form. Initially there is some default values (user name and address). When user click add, there is an extra input which user can enter another name and address, and the extra name and address will store in additionConfigs.
Example:
https://codesandbox.io/s/elastic-pateu-2uy4rt
import "./styles.css";
import { useState, useEffect } from "react";
export default function App() {
const [value, setValue] = useState([]);
const [additionConfigs, setAdditionConfigs] = useState([]);
useEffect(() => {
setTimeout(() => {
setValue([
{
id: 1,
baseName: "XXX",
config: {
name: "Kenny",
address: "New york"
}
},
{
id: 2,
baseName: "YYY",
config: {
name: "Ben",
address: "Boston"
}
},
{
id: 3,
baseName: "ZZZ",
config: {
name: "Mary",
address: "Los Angeles"
}
}
]);
}, 1000);
}, []);
const onAddBaseConfig = (item) => {
setAdditionConfigs((preValue) => [
...preValue,
{
id: item.id,
config: {
name: "",
address: ""
}
}
]);
};
console.log(additionConfigs);
const onChangeName = (e, id) => {
setAdditionConfigs((preValue) => {
const newValue = preValue.map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
name: e.target.value
}
};
}
return v;
});
return newValue;
});
};
const onChangeAddress = (e, id) => {
setAdditionConfigs((preValue) => {
const newValue = preValue.map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
address: e.target.value
}
};
}
return v;
});
return newValue;
});
};
return (
<>
{value.length > 0 &&
value.map((v, index) => (
<div className="item" key={index}>
<div className="item">
{v.config.name}
{v.config.address}
{additionConfigs.length > 0 &&
additionConfigs
.filter((config) => config.id === v.id)
.map((config) => (
<div>
<label>name</label>
<input
value={config.config.name}
onChange={(e) => onChangeName(e, config.id)}
/>
<label>address</label>
<input
value={config.config.address}
onChange={(e) => onChangeAddress(e, config.id)}
/>
</div>
))}
</div>
<button onClick={() => onAddBaseConfig(v)}>Add</button>
</div>
))}
</>
);
}
Currently, I use config.id to update the extra name and address, but there is an issue that if user add two or more extra name and address input, when updating the first one, the second will update, too.
How do I update respectively? Giving each group of input a flag?
Assuming that the component should not modify the base value as it is set by a useEffect, but keep a additionConfigs which need to support any amount of config inputs, perhaps one solution could be to make additionConfigs state an object.
The additionConfigs object could have id from base value as key and an array of configs as value, and perhaps each config need its own id, so that they can be controlled by the added input, without major refactor of the existing code structure.
Forked live with modifications: codesandbox
Perhaps try the following as an example:
Define additionConfigs state as an object:
const [additionConfigs, setAdditionConfigs] = useState({});
Update logic for additionConfigs when adding a config input:
(The id logic here is only adding previous id++, and should probably be replaced by a unique id generator in actual project)
const onAddBaseConfig = (item) => {
setAdditionConfigs((preValue) => {
const preConfigs = preValue?.[item.id];
const newId = preConfigs
? preConfigs.reduce((acc, cur) => (cur.id > acc ? cur.id : acc), 0) + 1
: 1;
return {
...preValue,
[item.id]: preConfigs
? [
...preConfigs,
{
id: newId,
config: {
name: "",
address: ""
}
}
]
: [
{
id: newId,
config: {
name: "",
address: ""
}
}
]
};
});
};
Update logic for a config input for name, a baseId is added as an argument as each base value can have multiple configs:
const onChangeName = (e, id, baseId) => {
setAdditionConfigs((preValue) => {
const newArr = preValue[baseId].map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
name: e.target.value
}
};
}
return v;
});
return { ...preValue, [baseId]: newArr };
});
};
Same but for address:
const onChangeAddress = (e, id, baseId) => {
setAdditionConfigs((preValue) => {
const newArr = preValue[baseId].map((v) => {
if (v.id === id) {
return {
...v,
config: {
...v.config,
address: e.target.value
}
};
}
return v;
});
return { ...preValue, [baseId]: newArr };
});
};
Output with the changes:
<>
{value.length > 0 &&
value.map((v, index) => (
<div className="item" key={index}>
<div className="item">
{v.config.name}
{v.config.address}
{additionConfigs?.[v.id] &&
additionConfigs?.[v.id].length > 0 &&
additionConfigs?.[v.id].map((config, index) => (
<div key={config.id}>
<label>name</label>
<input
value={config.config.name}
onChange={(e) => onChangeName(e, config.id, v.id)}
/>
<label>address</label>
<input
value={config.config.address}
onChange={(e) => onChangeAddress(e, config.id, v.id)}
/>
</div>
))}
</div>
<button onClick={() => onAddBaseConfig(v)}>Add</button>
</div>
))}
</>

A items List in React looses its all new data rows when edited previous rows, Using custom Uncontrolled Input component

Quick explanation of problem in this image. It's kind of product list where last row is empty and on filling quantity column a new row should appear. Problem is when you have added few new rows and you go back try changing previous rows the newly created rows disappears beyond that point where you made the change.
SANDBOX with all code replicating issue > https://codesandbox.io/s/react-hooks-playground-forked-xbkrub
Index (Main entry point)
import * as React from "react";
import { useState, useEffect } from "react";
import { render } from "react-dom";
import Input from "./Input";
function App() {
const [products, setProducts]: any = useState([]);
const getProductsAsync = () =>
new Promise((resolve, reject) => {
setTimeout(() => {
const prodsArray = [
{
id: 371337,
barcode: null,
name: "4x Cheese",
details: null,
tags: null,
unit: null,
productTimestamp: "2022-01-26T11:40:34.000Z",
activeDate: null,
deleteDate: null,
price: 1
},
{
id: 372093,
barcode: "",
name: "Capri Sun",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-05-11T13:43:37.000Z",
activeDate: "2022-05-11T13:43:37.000Z",
deleteDate: null,
categories: { "924": "Juices", "13524": "3 for £11" },
price: 1
},
{
id: 372552,
barcode: "",
name: "Hot Chocolate",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-05-25T11:15:40.000Z",
activeDate: "2022-05-25T11:15:40.000Z",
deleteDate: null,
categories: { "924": "Juices" },
price: 1
},
{
id: 372553,
barcode: "",
name: "Orange",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-05-25T11:16:06.000Z",
activeDate: "2022-05-25T11:16:06.000Z",
deleteDate: null,
categories: { "924": "Juices" },
price: 1
},
{
id: 372598,
barcode: "",
name: "PRODUCT",
details: "",
tags: "",
unit: null,
productTimestamp: "2022-06-06T14:22:00.000Z",
activeDate: "2022-06-06T14:22:00.000Z",
deleteDate: null,
price: 1
}
];
resolve(prodsArray);
}, 1000);
});
useEffect(() => {
(async () => {
const productObjects: any = await getProductsAsync();
//add a new row at the end for adding more data.
addNewEmptyRow(productObjects);
})();
}, []);
useEffect(() => {}, [products]);
const onPriceChange = (index, event) => {
if (event !== undefined && event.target !== undefined) {
//make sure you supply name="fieldName you want to target in your html tag i.e <input name="taxPercentage">"
const fieldName = event.target.getAttribute("name");
const inputValue = event.target.value;
if (inputValue !== undefined && inputValue !== null) {
const prodComps = products.map((item, currIndex) => {
if (currIndex === index) {
let row = { ...item };
row[fieldName] = inputValue;
return row;
} else {
return item;
}
});
setProducts([...prodComps]);
addNewEmptyRow([...prodComps]);
}
}
};
const addNewEmptyRow = (list: any, rowToAdd?: any) => {
const lastRow = rowToAdd || list[list.length - 1];
if (
lastRow.price !== undefined &&
lastRow.price !== null &&
lastRow.price.toString().length > 0
) {
// enableCrossButtonForLastRow(list);
const rows: any = [
...list,
{
price: "",
vatPercentage: "",
tags: "",
name: " ",
id: null,
autoGenIdForNewlyAddedRow: Date.now(),
showCross: false
}
];
setProducts(rows);
}
};
const generateList: any = () => {
return products.map((item: any, index) => {
return (
<React.Fragment key={item.id || item.autoGenIdForNewlyAddedRow}>
<div>
<input type="text" name="name" value={item.name} readOnly={true} />
</div>
<div>
<Input
type="text"
name="price"
value={item.price}
onChange={(e) => onPriceChange(index, e)}
/>
</div>
<div>X</div>
</React.Fragment>
);
});
};
return (
<div>
<div
style={{
display: "grid",
gridTemplateColumns: "[nameOverride] 175px [price] 155px [cross] 65px"
}}
>
{generateList()}
</div>
</div>
);
}
render(<App />, document.getElementById("root"));
Input.tsx (Custom input component)
import * as React from "react";
import { useEffect, useRef, useState } from "react";
const Input = (props) => {
const [value, setValue] = useState("");
const inputRef: any = useRef(null);
const valueRef = useRef("");
useEffect(() => {
inputRef.current.addEventListener("input", handleChange);
}, []);
useEffect(() => {
if (Boolean(props.value)) {
//this logic sets the data to 2 decimal places
if (props.decimalplaces) {
const val = parseFloat(props.value).toFixed(props.decimalplaces);
valueRef.current = val;
setValue(val);
} else {
valueRef.current = props.value;
setValue(props.value);
}
}
}, [props]);
const handleChange = (e) => {
const val = e.target.value;
if (props.decimalplaces && val.match(/\./g)) {
const [, decimal] = val.split(".");
// restrict value to only 2 decimal places
if (decimal?.length > props.decimalplaces) {
inputRef.current.value = valueRef.current;
} else {
valueRef.current = e.target.value;
}
} else {
valueRef.current = val;
}
if (Boolean(props.onChange)) {
//this builds the custom event object similar to the javascript event object.
props.onChange(
buildEventObject({
value: valueRef.current,
name: props.name,
type: props.type
})
);
}
};
const buildEventObject = (params) => {
return {
target: {
value: params.value,
getAttribute: (key) => {
return params[key];
}
}
};
};
return (
<div>
<input type={props.type} ref={inputRef} defaultValue={value} />
</div>
);
};
export default Input;

Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'reduce')

I am working on yup validation. Whenever it comes to FileList validation, if I left the input blank, I got the following error:
enter image description here
Here is my code:
import React, { useEffect, useMemo, useState } from 'react';
import { useLocation } from 'react-router';
import { useDispatch, useSelector } from '#app/hooks';
import { Button, Dimmer, Dropdown, DropdownItemProps, DropdownProps, Form, Header, Loader, Modal } from 'semantic-ui-react';
import { NewBook, TestBook, UpdateParam } from '#app/reducers/master-data/book/book.model';
import * as yup from "yup";
import { useForm } from 'react-hook-form';
import { yupResolver } from '#hookform/resolvers/yup';
import { useHistory } from 'react-router-dom';
import { _getAllGenres } from '#app/reducers/master-data/genre/genre.slice';
import { _getAllPublisher } from '#app/reducers/master-data/publisher/publisher.slice';
import { _getAllAuthors } from '#app/reducers/master-data/author/author.slice';
import { _getAllDepartment } from '#app/reducers/master-data/department/department.slice';
import { _updateBook, _getAllBooks, _getBookDetail, _uploadBookFile } from '#app/reducers/master-data/book/book.slice';
import { Wrapper } from '#app/components/data-table/StyledComponent';
import { IBookParam } from '#app/reducers/master-data/book/book.service';
interface IProps {
}
interface ErrMessage {
type: string,
message: string
}
const schema = yup.object().shape({
title: yup.string().required('Title cannot blank'),
publishYear: yup.number(),
isPublic: yup.boolean(),
language: yup.string(),
authorIDs: yup.array(),
genreIDs: yup.array(),
description: yup.string(),
// departmentIDs: yup.array(),
publisherID: yup.number(),
file: yup
.mixed()
.required("A random message")
.nullable(false)
.test("Empty file", "You need to provide a file", (value: FileList) => {
let x = value.length !== 0
debugger
return value.length !== 0;
})
.test("type", "Only support PDF file", (value: FileList) => {
return value[0].type === "application/pdf"
})
});
const UpdateBookPage: React.FC<IProps> = (props) => {
const dispath = useDispatch()
const history = useHistory();
useEffect(() => {
dispath(_getAllGenres());
dispath(_getAllPublisher());
dispath(_getAllAuthors());
dispath(_getAllDepartment());
// dispath(_getAllBooks());
dispath(_getBookDetail(id));
}, [])
const search = useLocation().search;
const query = new URLSearchParams(search);
const id = query.get('id') || '';
const genres = useSelector(state => state.genre).genres;
const publishers = useSelector(state => state.publisher).publishers;
const authors = useSelector(state => state.author).authors;
const departments = useSelector(state => state.department).departments;
const book = useSelector(state => state.book.selectedBook);
const isLoading = useSelector(state => state.book.isLoading);
const statusCode = useSelector(state => state.book.statusCode);
// console.log(book)
const defaultDepartments = useMemo<number[]>(() => {
let temp: number[] = []
for (let x of book.departments) {
temp.push(x.id)
};
return temp;
}, [book.departments])
const defaultAuthors = useMemo<number[]>(() => {
let temp: number[] = []
for (let x of book.authors) {
temp.push(Number(x.id))
};
return temp;
}, [book.authors])
const defaultGenres = useMemo<number[]>(() => {
let temp: number[] = []
for (let x of book.genres) {
temp.push(Number(x.id))
};
return temp;
}, [book.genres])
const departmentOptions = useMemo<DropdownItemProps[]>(() => {
let temp: DropdownItemProps[] = []
for (let x of departments) {
temp.push({
key: x.id,
text: x.name,
value: x.id
})
};
return temp;
}, [departments])
const genreOptions = useMemo<DropdownItemProps[]>(() => {
let temp: DropdownItemProps[] = []
for (let x of genres) {
temp.push({
key: x.id,
text: x.name,
value: x.id
})
};
return temp;
}, [genres])
const authorOptions = useMemo<DropdownItemProps[]>(() => {
let temp: DropdownItemProps[] = []
for (let x of authors) {
temp.push({
key: x.id,
text: x.fullName,
value: x.id
})
};
return temp;
}, [authors])
const [openForm, setOpenForm] = useState<boolean>(false)
const [authorSelected, setAuthorSelected] = useState<Number[]>([...defaultAuthors]);
const [genreSelected, setGenreSelected] = useState<Number[]>([...defaultGenres]);
const [departmentSelected, setDepartmentSelected] = useState<Number[]>([...defaultDepartments]);
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const [fileErrorText, setFileErrorText] = useState<string>('');
// const [selectedFile, setSelectedFile] = useState<File|null>(null);
const [isOpen, setIsOpen] = useState<boolean>(false);
const [alertText, setAlertText] = useState<string>('');
const [isFailed, setIsFailed] = useState<boolean>(false);
const [errMessage, setErrMessage] = useState<ErrMessage>({
type: '',
message: ''
})
const { register, handleSubmit, formState: { errors }, setValue, trigger } = useForm<TestBook>({
resolver: yupResolver(schema),
// defaultValues: defaultBook
});
useEffect(() => {
if (book) {
setInitValue()
}
}, [book])
const setInitValue = () => {
setValue('title', book.title)
setValue('publishYear', book.publishYear)
setValue('authorIDs', [...defaultAuthors])
setValue('language', 'vi')
setValue('departmentIDs', [...defaultDepartments])
setValue('description', book.description)
setValue('genreIDs', [...defaultGenres])
setValue('isPublic', book.isPublic)
setValue('publisherID', Number(book.publisher.id))
}
// console.log(defaultAuthors, defaultDepartments, defaultGenres)
const handleSubmitBtn = async (data: NewBook) => {
debugger
console.log(data);
const valid = validateData();
debugger
if (valid) {
data.authorIDs = authorSelected.concat(defaultAuthors);
data.genreIDs = genreSelected.concat(defaultGenres)
data.departmentIDs = departmentSelected.concat(defaultDepartments)
const updateParam: UpdateParam = {
id: id,
newBook: data
}
console.log(data);
console.log(selectedFile);
try {
debugger
await dispath(_updateBook(updateParam)).then(() => {
if (statusCode == 200) {
if (selectedFile != null) {
let formData = new FormData();
formData.append("FILE", selectedFile);
formData.append("NAME", selectedFile.name);
const param: IBookParam = {
id: localStorage.getItem("BOOKID") || '',
file: formData
}
console.log(param.file);
console.log(param.id);
dispath(_uploadBookFile(param)).then(() => {
if (statusCode == 200) {
setIsOpen(true);
console.log(isOpen);
setAlertText("Update successfully")
} else {
setIsOpen(true)
setAlertText("An error has occured. Please try again later");
setIsFailed(true);
}
})
} else {
return;
}
}
console.log(data);
})
}
catch {
setIsOpen(true)
setAlertText("An error has occured. Please try again later");
setIsFailed(true);
}
}
}
const validateData = (): boolean => {
let x = authorSelected.concat(defaultAuthors)
let y = genreSelected.concat(defaultGenres)
let z = departmentSelected.concat(defaultDepartments)
debugger
if (authorSelected.concat(defaultAuthors).includes(0)) {
setErrMessage({
type: "AUTHOR",
message: "At least one author must be selected"
});
return false;
} else if (genreSelected.concat(defaultGenres).includes(0)) {
setErrMessage({
type: "GENRE",
message: "At least one genre must be selected",
});
return false;
} else if (departmentSelected.concat(defaultDepartments).includes(0)) {
setErrMessage({
type: "DEPARTMENT",
message: "At least one department must be selected",
});
return false;
}
return true;
}
const handleAddItem = (data: DropdownProps, type: string) => {
let val = JSON.stringify(data.value)
let numArr: Number[] = []
val = val.substring(1, val.length - 1)
let valArr = val.split(',');
// console.log(valArr);
for (let x of valArr) {
numArr.push(Number(x))
// console.log(numArr)
}
console.log(numArr)
// const y: Number[] = numArr
switch (type) {
case "AUTHOR":
setValue("authorIDs", numArr)
setAuthorSelected(numArr)
break;
case "GENRE":
setValue("genreIDs", numArr)
setGenreSelected(numArr);
break;
case "DEPARTMENT":
// if(numArr.length > 5){
// setErrMessage({
// type: "Department",
// message: "You can only select maximum of 5 departments"
// })
// break;
// }
setValue("departmentIDs", numArr)
setDepartmentSelected(numArr);
break;
}
}
const handleSelectFile = (event: any) => {
console.log(event.target.files[0]);
setSelectedFile(event.target.files[0]);
}
const handleCancelAction = () => {
history.push('/master-data/manage-book')
}
useEffect(() => {
console.log(authorSelected)
console.log(genreSelected)
console.log(departmentSelected)
}, [authorSelected, genreSelected, departmentSelected])
return (
<div className="form-create-book">
<Header>Update book</Header>
{
isLoading && (
<Wrapper style={{ padding: '40px 0' }}>
<Dimmer inverted active={true}>
<Loader>Loading</Loader>
</Dimmer>
</Wrapper>)
}
{
!isLoading && (
<div>
<Modal
size={"tiny"}
open={isOpen}
>
<Modal.Content>
<h2 style={{textAlign: 'center'}}>{alertText}</h2>
</Modal.Content>
<Modal.Actions>
{!isFailed &&
<Button negative onClick={() => setIsOpen(false)}>
Close
</Button>
}
<Button positive onClick={() => history.push("/librarian/master-data/manage-book")}>
Ok
</Button>
</Modal.Actions>
</Modal>
<Form className="create-book-form" onSubmit={handleSubmit(handleSubmitBtn)}>
<Form.Field>
<label className='dot-required'>Title</label>
<input {...register('title')} className="text-field" />
<p>{errors.title?.message}</p>
</Form.Field>
<Form.Field>
<label>Description</label>
<textarea {...register('description')} className="textarea-field" />
<p>{errors.description?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Publish Year</label>
<input className="text-field" {...register('publishYear')} />
<p>{errors.publishYear?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>PDF File</label>
<input {...register('file')} className="text-field" accept="application/pdf" type="file" name="file" onChange={handleSelectFile} />
<p>{errors.file?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Access Permission</label>
<select defaultValue={book.isPublic ? 1 : 0} className="text-field" {...register('isPublic')}>
<option value={1}>Public</option>
<option value={0}>Restricted</option>
</select>
<p>{errors.isPublic?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Publisher</label>
<select className="text-field" {...register('publisherID')} >
{
publishers.map((e) => {
if (e.id === book.publisher.id)
return (<option selected value={e.id}>{e.name}</option>)
else
return (<option value={e.id}>{e.name}</option>)
}
)
}
</select>
<p>{errors.publisherID?.message}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Authors</label>
<Dropdown className="text-field" onChange={(e, data) => handleAddItem(data, "AUTHOR")} placeholder='Authors' fluid multiple selection options={authorOptions} defaultValue={defaultAuthors} />
<p>{errMessage.type == "AUTHOR" ? errMessage.message : ''}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Genre</label>
<Dropdown className="text-field" onChange={(e, data) => handleAddItem(data, "GENRE")} placeholder='Genres' fluid multiple selection options={genreOptions} defaultValue={defaultGenres} />
<p>{errMessage.type == "GENRE" ? errMessage.message : ''}</p>
</Form.Field>
<Form.Field>
<label className='dot-required'>Department</label>
<Dropdown className="text-field" onChange={(e, data) => handleAddItem(data, "DEPARTMENT")} placeholder='Genres' fluid multiple selection options={departmentOptions} defaultValue={defaultDepartments} />
<p>{errMessage.type == "DEPARTMENT" ? errMessage.message : ''}</p>
</Form.Field>
<Button
content="Submit"
// onClick={handleSubmit(handleSubmitBtn)}
className="ui inverted green button"
/>
<Button
color="black"
content="Cancel"
onClick={() => handleCancelAction()}
className="negative ui button"
/>
</Form>
</div>
)
}
</div>
);
};
export default UpdateBookPage;
When I check the source tab, I find out that the problem comes from here:
But I don't know how to solve it.
replace
.required("A random message")
.nullable(false)
to
.test("fileLength", "A random message", (value) => {
return !!value.length
})

How to make field validation?

How to make field validation?
I have an object with fields from which I generate a form, and when submitting, I need to check each field so that it is not empty, I do this, but it doesn’t work
My form:
const [volunteerData, setVolunteerData] = useState({
fullName: {
value: '',
type: "text",
placeholder: "Name",
label: "Name"
},
phone: {
value: '',
type: "text",
placeholder: "Phone number",
label: "Phone number",
mask: "+7(999) 999 99 99"
}
)}
Render form:
const onHandleRenderForm = () => {
return Object.keys(volunteerData).map((items, idx) => {
const control = volunteerData[items];
return (
<div key={idx} className="content-data-box">
<label>{control.label}</label>
<InputMask
type={control.type}
placeholder={control.placeholder}
mask={control.mask}
onChange={e => onHandleFormData(e, items)}
/>
</div>
)
})
};
onChange input:
const onHandleFormData = (e, items) => {
const before = {...volunteerData};
const after = {...before[items]}
after.value = e.target.value;
before[items] = after;
setVolunteerData(before);
};
onClick (submit button):
const onHandleErrorBoundary = () => {
Object.keys(volunteerData).map(items => {
const errorData = items.value === '';
console.log(errorData)
})
};
Change items.value === '' to volunteerData[items].value !== ""
const onHandleErrorBoundary = () => {
Object.keys(volunteerData).map(items => {
const errorData = volunteerData[items].value !== "";
return console.log(errorData);
});
};
you can check here codesandbox

How to Disable Row in Antd Table

so I worked on a project using react js with umi js and antd as additional dependencies,
I had a problem when I got the task to disable each row in the antd table,
I tried to read the documentation antd but got nothing,
is it possible that you can do that? or there is another possible way to doing that
Thank you for the help
here's my code :
/* eslint-disable */
import React, { useState, useEffect, useRef } from 'react';
import { Modal, Button, Select, message, Radio, Table, Alert } from 'antd';
import _ from 'lodash';
import axios from 'axios';
import cookies from 'js-cookie';
import {_getCurrentBusiness} from '../../../utils/utils_business';
import {formatMessage } from 'umi-plugin-locale';
function DeleteConfirm (props) {
const user_auth = cookies.getJSON('ckmsbp');
const business = _getCurrentBusiness();
const [radio, setRadio] = useState('all');
const [role, setRole] = useState(null);
const [chRole, setChrole] = useState(null); //changerole
const [btn, setBtn] = useState(false);
const isMounted = useRef(null);
const roleRef = useRef(null);
const spanAmount = {fontSize: '1rem', fontWeight: 500,marginLeft: '1rem'}
useEffect( () => {
isMounted.current = true;
return () => isMounted.current = false;
}, [])
useEffect( () => {
if(!_.isNil(props.roles)) {
const updateRole = _.filter(props.roles, r => !_.eq(r.id, props.role.id) );
setRole(updateRole); //tampil di select
}
}, [props.roles]);
const handleSubmit = async () => {
let accountMeta = {}
const body = {status: 'deleted'}
const params = { _id: props.role.id}
console.log('radio', radio);
if(_.eq(radio, 'all')){
if(_.isNil(chRole)) {
message.error('data can not empty')
props.chVisible(null); //close modal
}
_.forEach(props.account, async acc => {
let bus = [];
if( !_.isNil(acc.business) && _.isString(acc.business) ) bus = JSON.parse(acc.business);
const find = _.findIndex(bus, b => {
return _.eq(b._id, business._id) && _.eq(b.role_capability, props.role.id)
})
bus[find].role_capability = chRole;
acc.business = JSON.stringify(bus)
accountMeta = {
value : acc.business,
key : 'business',
account_id: acc._id
}
await axios.put(`${API}/account-meta`, accountMeta, { headers: { Authorization: user_auth.token}});
})
} else if( _.eq(radio, 'manual')){
console.log('asd');
} else if (_.eq(radio, 'delete')){
_.forEach(props.account, async acc => {
let bus = [];
if( !_.isNil(acc.business) && _.isString(acc.business) ) bus = JSON.parse(acc.business);
const find = _.findIndex(bus, b => _.eq(b._id, business._id) && _.eq(b.role_capability, props.role.id) )
if(_.gt(find, -1)){
acc.business = JSON.stringify([])
accountMeta = {
value : acc.business,
key : 'business',
account_id: acc._id
}
await axios.put(`${API}/account-meta`, accountMeta, { headers: { Authorization: user_auth.token}});
}
})
}
const deleteResult = await axios.put(`${API}/master`, body, { params, headers: { Authorization: user_auth.token}});
if(!_.isNil(deleteResult) && _.isObject(deleteResult.data)){
let no = 1;
let data = []
let updateRole = _.filter(props.roles, r => !_.eq(r.id, props.role.id));
_.map(updateRole, role => {
role.no = no;
data.push(role)
no++
});
props.data(data); //tampil di select
message.success('data updated!')
props.chVisible(null); //close modal
}
}
const onChange = (data) => {
const value = data.target.value
setRadio(value);
}
const roleChange = (data) => {
setChrole(data)
}
//props column diambil dari datasource
const columns = [
{
title : 'No',
dataIndex: 'no',
key : 'no',
},
{
title : 'Account\'s Name',
dataIndex: 'name',
key : 'name',
},
{
title : 'Change Role',
dataIndex: 'id',
key : 'action',
render : (text, data) => renderButton(text, data)
},
];
const handleClick = (e, data) => {
setBtn(!btn)
console.log('e', e);
console.log('data', data);
}
const rowClassName = (record, index) => {
console.log('record', record);
console.log('index',index);
}
const renderButton = () => {
let arrayAllow = [];
arrayAllow.push(
<Select
showSearch
key = '1'
size = "small"
placeholder = "select"
ref = {roleRef}
optionFilterProp = "children"
style = {{ width: 100 }}
onChange = {(e) => roleChange(e)} //handle change role
filterOption = {(input, option) => _.toLower(option.props.children).indexOf(_.toLower(input)) >= 0}
>
{
!_.isNil(role) && _.map(role, (newVal) => {
return (<Select.Option
key = {newVal.id}
title = {newVal.title}
value = {newVal.id}>{newVal.title}
</Select.Option>)
})
}
</Select>
)
arrayAllow.push( <Button
type = {!btn ? "danger" : "primary"}
key = '2'
icon = {!btn ? "close" : "redo"}
size = "small"
onClick = {(e) => handleClick(e, props.role.id)}
/> )
return arrayAllow
}
// R E N D E R I N G
return(
<div>
<Modal
title = {`${formatMessage({id: 'ROLE_MANAGEMENT.DELETE_CONFIRM_TITLE'})} ${props.role.title}`}
visible = {props.visible}
onOk = {() => handleSubmit()}
onCancel = {() => props.cancel(null) }
width = {800}
>
<p>{formatMessage({id : 'ROLE_MANAGEMENT.DELETE_CONFIRM_STATEMENT', title: props.role.title})}</p>
<div style={{marginBottom: '1rem'}}>
<Radio.Group onChange = {(e) => onChange(e)} value={radio}>
<Radio value="all" >Changed All of people </Radio>
<Radio value="manual">Changed people manually</Radio>
<Radio value="delete">Total delete </Radio>
</Radio.Group>
</div>
{ _.eq(radio, 'all') &&
<div>
<Select
showSearch
ref = {roleRef}
size = "large"
style = {{ width: 200 }}
placeholder = {formatMessage({id: 'ACCOUNT.PLCHOLDER_ROLE'})}
optionFilterProp = "children"
onChange = {(e) => roleChange(e)} //handle change role
filterOption = {(input, option) => _.toLower(option.props.children).indexOf(_.toLower(input)) >= 0}
>
{
!_.isNil(role) && _.map(role, (newVal) => {
return ( <Select.Option
key = {newVal.id}
title = {newVal.title}
value = {newVal.id}
>{newVal.title}
</Select.Option> )
})
}
</Select>
<span style={spanAmount}>{`Total amount of people which have role ${props.role.title} : ${_.size(props.account)}`}</span>
</div>
}
{ _.eq(radio, 'manual') && <Table
dataSource = {props.account}
rowClassName = {rowClassName}
columns = {columns}
pagination = {{ pageSize: 50 }}
scroll = {{ y: 250 }}
/>
}
{ _.eq(radio, 'delete') && <Alert
message = "Attention!"
description = {formatMessage({id: 'ROLE_MANAGEMENT.DELETE_CONFIRM_DELETE'})}
type = "warning"
showIcon
/>
}
</Modal>
</div>
)
}
export default DeleteConfirm;
*the picture that I intend to disable when clicked on the danger button
In Antd there is no simple way to disable a row, so you can do it as workaround like below
So basically when you click on close button you can have state whether its been enabled or disabled as a boolean value
so each record will have that key. so based on that you can add a className and style it as disabled.
Here is a sample code snippet
App.js
import React from "react";
import ReactDOM from "react-dom";
import "antd/dist/antd.css";
import { Table } from "antd";
import "./styles.css";
function App() {
const dataSource = [
{
key: "1",
name: "Mike",
age: 32,
address: "10 Downing Street",
enabled: true
},
{
key: "2",
name: "John",
age: 42,
address: "10 Downing Street",
enabled: false
}
];
const columns = [
{
title: "Name",
dataIndex: "name",
key: "name"
},
{
title: "Age",
dataIndex: "age",
key: "age"
},
{
title: "Address",
dataIndex: "address",
key: "address"
}
];
return (
<>
<Table
dataSource={dataSource}
columns={columns}
rowClassName={record => !record.enabled && "disabled-row"}
/>
;
</>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
style.css
.disabled-row {
background-color: #dcdcdc;
pointer-events: none;
}
I hope this way you will have better understanding of solving the problem
Working codesandbox

Resources