Formik React set values onChange with setFieldValue outside Array of Fields - reactjs

My goal is to select a Student and with onChange to dynamically setFieldValues for the FieldArray of their nested schedule array of objects.
When I select Student: 1, I should get an array of fields for each of their 3 schedule items, Student 2: will have example 4 items. Example relates to something such as this older example with Fruit on CodeSandbox. A second CodeSandbox also related for this.
My problem is that when I try to map a student's schedule it shows schedule.map is a TypeError. Thus not populating my FieldArray with the fields to be edited.
TypeError: values.schedule.map is not a function
My JSON data:
{
"count": 2,
"results": [
{
"id": "1",
"name": "Tom",
"schedule": [
{
"class": "GYM",
"time": "9:00,
},
{
"class": "History",
"time": "10:00",
},
{
"class": "Lunch",
"time": "11:00",
},
},
{
"id": "2",
"name": "Nancy",
"schedule": [
{
"class": "Math",
"time": "9:00,
},
{
"class": "Science",
"time": "10:00",
},
{
"class": "English",
"time": "11:00",
},
{
"class": "History",
"time": "12:00",
},
}
]
}
My Formik Component:
const NewStudentSchedule = () => {
const [studentSchedule, setSchedule] = useState([]);
useEffect(() => {
const getSchedule = async () => {
try {
const { data } = await fetchContext.authAxios.get(
`/api/schedule/`
);
setSchedule(data.results);
// console.log(data);
} catch (err) {
console.log(err);
}
};
getSchedule();
}, [fetchContext]);
const initialValues = {
id: '',
schedule: [
{
class: '',
time: '',
},
],
};
return (
<Formik
initialValues={initialValues}
onSubmit={async (values, { resetForm }) => {
alert(JSON.stringify(values, null, 2));
resetForm();
}}
>
{({
isSubmitting,
values,
setFieldValue,
handleChange,
errors,
touched,
}) => (
<Form>
<div className="row">
<div className="col-md-6 mr-auto">
<div className="form-group">
<label htmlFor="status">STUDENT</label>
<Field
className="form-control"
as="select"
name="id"
onChange={(event) => {
handleChange(event);
const selectedTask = schedule.find(
(selectStudent) => selectStudent.id === event.target.value
);
setFieldValue(
`schedule.step`,
selectedTask.schedule.step
);
}}
>
<option value="" defaultValue disabled>
...
</option>
{schedule &&
schedule.map((i) => (
<option key={i.id} value={i.id}>
{i.name}
</option>
))}
</Field>
</div>
</div>
</div>
<div className="col-md-12">
<h3 className="pt-3">CREATE SCHEDULE</h3>
<FieldArray name="schedule">
{({ insert, remove, push }) => (
<>
{values.schedule.length > 0 &&
values.schedule.map((task, index) => (
<div className="row" key={index}>
<div className="col-11">
<div className="row">
<div className="col">
<div className="form-group">
<label
htmlFor={`schedule.${index}.class`}
>
CLASS
</label>
<Field
className="form-control"
name={`schedule.${index}.class`}
type="text"
/>
</div>
</div>
<div className="col">
<div className="form-group">
<label
htmlFor={`schedule.${index}.time`}
>
TIME
</label>
<Field
className="form-control"
name={`schedule.${index}.time`}
type="text"
/>
</div>
</div>
</div>
</div>
<div className="col-1">
<button
type="button"
className="btn delete"
onClick={() => remove(index)}
>
Delete
</button>
</div>
</div>
))}
<button
type="button"
className="btn add"
onClick={() => push({ class: '', time: '' })}
>
Add
</button>
</>
)}
</FieldArray>
<button
className="btn float-right mb-5"
type="submit"
disabled={isSubmitting}
>
SUBMIT
</button>
</div>
</Form>
)}
</Formik>
)
}
Screenshots of my App:

This is a tricky one 😉
It looks like you want to pre-populate a form with data fetched from the API. Also, you'd like the user to be able to switch between students (Tom and Nancy). In that case, you need to set all students' data inside Formik's initialValues. And when handling the class values inside the <FieldArray>, you need to ensure you are referring to the correct class for that particular student.
There are a few issues with your code, for example studentSchedule is never used and schedule is undefined. I went ahead and made some assumptions and came up with this solution below. It refers to the correct class by using two indexes inside the <FieldArray>: selectedStudentIndex and taskIndex
const App = () => {
const [studentSchedule, setSchedule] = useState([]);
useEffect(() => {
const getSchedule = async () => {
try {
const { data } = await getData();
setSchedule(data.results);
// console.log(data);
} catch (err) {
console.log(err);
}
};
getSchedule();
}, []);
return <ScheduleForm students={studentSchedule} />;
};
const ScheduleForm = ({ students }) => {
const initialValues = {
id: "",
students
};
return (
<Formik
enableReinitialize={true}
initialValues={initialValues}
onSubmit={async (values, { resetForm }) => {
console.log(JSON.stringify(values, null, 2));
}}
>
{({
isSubmitting,
values,
setFieldValue,
handleChange,
errors,
touched
}) => {
const selectedStudentIndex = values.students.findIndex(
({ id }) => id === values.id
);
return (
<Form>
<div className="row">
<div className="col-md-6 mr-auto">
<div className="form-group">
<label htmlFor="status">STUDENT</label>
<Field
className="form-control"
as="select"
name="id"
onChange={(event) => {
handleChange(event);
const selectedTask = students.find(
(selectStudent) =>
selectStudent.id === event.target.value
);
setFieldValue(
`schedule.step`,
selectedTask.schedule.step
);
}}
>
<option value="" defaultValue disabled>
...
</option>
{values.students &&
values.students.map((i) => (
<option key={i.id} value={i.id}>
{i.name}
</option>
))}
</Field>
</div>
</div>
</div>
<div className="col-md-12">
<h3 className="pt-3">CREATE SCHEDULE</h3>
<FieldArray name={`students.${selectedStudentIndex}.schedule`}>
{({ insert, remove, push }) => (
<div>
{values.id !== "" &&
values.students[selectedStudentIndex].schedule.map(
(task, taskIndex) => (
<div className="row" key={taskIndex}>
<div className="col-11">
<div className="row">
<div className="col">
<div className="form-group">
<label
htmlFor={`students.${selectedStudentIndex}.schedule.${taskIndex}.class`}
>
CLASS
</label>
<Field
className="form-control"
name={`students.${selectedStudentIndex}.schedule.${taskIndex}.class`}
type="text"
/>
</div>
</div>
<div className="col">
<div className="form-group">
<label
htmlFor={`students.${selectedStudentIndex}.schedule.${taskIndex}.time`}
>
TIME
</label>
<Field
className="form-control"
name={`students.${selectedStudentIndex}.schedule.${taskIndex}.time`}
type="text"
/>
</div>
</div>
</div>
</div>
<div className="col-1">
<button
type="button"
className="btn delete"
onClick={() => remove(taskIndex)}
>
Delete
</button>
</div>
</div>
)
)}
<button
type="button"
className="btn add"
onClick={() => push({ class: "", time: "" })}
>
Add
</button>
</div>
)}
</FieldArray>
<button
className="btn float-right mb-5"
type="submit"
disabled={isSubmitting}
>
SUBMIT
</button>
</div>
</Form>
);
}}
</Formik>
);
};
Live Demo
I'd be interested if anyone comes up with a simpler approach.

Related

How to get different data from a dynamically render input field react js

I have state,
const [state, setState] = useState({
semester: '',
credit: '',
sgpa: ''
})
I have taken an input field where user can give his value.According to the user input dynamicallyinputfield will be created.So here my issue is 'I want different data from each dynamically created field'.How can I achive that?
Screenshot of form
my whole component is,
import React, { useState } from "react";
import cal from "./image/bgimg.jpg";
function Home() {
const [state, setState] = useState({
semester: "",
credit: "",
sgpa: "",
});
const [noOfSem, setNoOfSem] = useState([]);
const handleChange = (e) => {
setState({ ...state, [e.target.name]: e.target.value });
};
const handleClick = () => {
let sems = [];
for (let index = 0; index < state?.semester; index++) {
sems.push(index + 1);
}
console.log(sems);
setNoOfSem(sems);
};
const emptySemester = () => {
setState({ ...state, semester: "" });
setNoOfSem([]);
};
const handleCgpaSubmit = () => {
console.log(state.credit, state.sgpa);
};
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<img src={cal} alt="" className="imgcal img-fluid" />
</div>
<div className="col-md-6">
<div className="col-md">
<div className="form1">
<div className="col-md formmain">
<input
type="number"
value={state?.semester}
name="semester"
onChange={handleChange}
placeholder="Enter Total Semester"
/>
{noOfSem.length === 0 ? (
<button
type="button"
className="btn btn-success btn333"
onClick={handleClick}
>
Submit
</button>
) : (
<button
type="button"
className="btn btn-success btn333"
onClick={emptySemester}
>
Reset Semester
</button>
)}
</div>
<div className="col form2">
{noOfSem?.map((item, index) => (
<>
<div className="col-md seminpt">
<label htmlFor="">Semester {index + 1}</label>
<input
type="Number"
name="credit"
value={state.credit}
onChange={handleChange}
placeholder="Total Credit"
/>
<input
type="Number"
name="sgpa"
value={state.sgpa}
onChange={handleChange}
placeholder={`SGPA sem${index + 1}`}
/>
</div>
</>
))}
{noOfSem.length > 0 && (
<button
type="button"
className="btn btn-success btn3334"
onClick={handleCgpaSubmit}
>
Submit
</button>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Home;
'I want different data from each dynamically created field'.How can I achive that?
Thanks in advance....
I changed the input for the number of semester to have a ref, since there is no need to update a state everytime you change the input. When calling handleClick it will take the value of the input and update noOfSem.
The handleChange takes in a semester index, this will help keep track of which data belongs to which semester. We return a new state with and override the prevState on the given semesterIndex.
When rendering the we just create a new array the size of noOfSem. Getting the values from the state we simply pass in the semesterIndex and pick the property we want.
function Home() {
const [state, setState] = useState({});
const [noOfSem, setNoOfSem] = useState(0);
const semesterNumberInputRef = useRef(null);
const handleChange = (e, semesterIndex) => {
setState((prevState) => {
return {
...prevState,
[semesterIndex]: {
...prevState[semesterIndex],
[e.target.name]: e.target.value,
},
};
});
};
const handleClick = () => {
if (!semesterNumberInputRef.current) return;
const newNumberOfSemesters = Number(semesterNumberInputRef.current.value);
setNoOfSem(newNumberOfSemesters);
};
const emptySemester = () => {
setState({});
setNoOfSem(0);
};
const handleCgpaSubmit = () => {
console.log(state);
};
return (
<div className="container">
<div className="row">
<div className="col-md-6">
<div className="col-md">
<div className="form1">
<div className="col-md formmain">
<input
ref={semesterNumberInputRef}
type="number"
name="semester"
placeholder="Enter Total Semester"
/>
{noOfSem === 0 ? (
<button
type="button"
className="btn btn-success btn333"
onClick={handleClick}
>
Submit
</button>
) : (
<button
type="button"
className="btn btn-success btn333"
onClick={emptySemester}
>
Reset Semester
</button>
)}
</div>
<div className="col form2">
{new Array(noOfSem).fill(null).map((_, index) => {
const semesterIndex = index + 1; // add one since the index starts at 0
return (
<div
className="col-md seminpt"
key={`semester-input-${index}`}
>
<label>Semester {index + 1}</label>
<input
type="number"
name="credit"
value={state[semesterIndex]?.credit || ""}
onChange={(e) => handleChange(e, semesterIndex)}
placeholder="Total Credit"
/>
<input
type="number"
name="sgpa"
value={state[semesterIndex]?.sgpa || ""}
onChange={(e) => handleChange(e, semesterIndex)}
placeholder={`SGPA sem${semesterIndex}`}
/>
</div>
);
})}
{noOfSem > 0 && (
<button
type="button"
className="btn btn-success btn3334"
onClick={handleCgpaSubmit}
>
Submit
</button>
)}
</div>
</div>
</div>
</div>
</div>
</div>
);
}
handleCgpaSumbit will log this as result, an object with the number of the semester as index and the according data.
{
"1": {
"credit": "20",
"sgpa": "10"
},
"2": {
"credit": "21",
"sgpa": "9"
},
"3": {
"credit": "13",
"sgpa": "6"
},
"4": {
"credit": "8",
"sgpa": "6"
}
}
I hope this helps you with your project!

React Formik JSON to YAML, then back to JSON on Submit

My goal is to have a JSON object stringified to YAML for a field value then, on form submit, take the YAML and convert it to JSON.
I was able to take the JSON, and use YAML.stringify(options, null, 2); to get a YAML value in the field to be edited with YAML node package. However, upon submit I'm not exactly sure how to parse the YAML as JSON.
Near the bottom of my component I tried to push JSON.stringify('', null, 0) to get the value but I keep getting YAML.
How can I get the edited YAML field to parse back to JSON for the data with Formik's onSubmit?
Is taking edited YAML and converting it as JSON on submit something that is possible on the frontend or would I have to add backend logic? I found converters but not much in this instance.
My JSON data (focus is on "options" key):
{
"id": "1",
"photo_name": "Testing",
"options": {
"test": true,
"command": [
"test photo"
],
"max_size": 50,
"is_prio": false
},
YAML output:
test: true
command:
- test photo
max_size: 50
is_prio: false
My React component:
import YAML from 'yaml';
const NewPhoto = () => {
const fetchContext = useContext(FetchContext);
const [photoList, setphotoList] = useState([]);
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
useEffect(() => {
const getphotoList = async () => {
try {
const { data } = await fetchContext.authAxios.get(`/api/photo_data/`);
setphotoList(data.results);
} catch (err) {
console.log(err);
}
};
getphotoList();
}, [fetchContext]);
const initialValues = {
id: '',
options: '',
};
return (
<div className="col-xl-12">
<Formik
initialValues={initialValues}
onSubmit={async (values, { resetForm }) => {
alert(JSON.stringify(values, null, 2));
resetForm();
}}
validationSchema={schema}
>
{({ isSubmitting, values, setFieldValue, handleChange }) => (
<Form>
<FieldArray name="photo">
{({ insert, remove, push }) => (
<>
{values.photo.length > 0 &&
values.photo.map((task, index) => (
<div className="row" key={index}>
<div className="col-11">
<div className="row">
<div className="col">
<div className="form-group">
<label htmlFor={`photo.${index}.step`}>
PHOTO TASK
</label>
<Field
className="form-control"
as="select"
name={`photo.${index}.id`}
onChange={(event) => {
handleChange(event);
const selectedPhotoTask = photoList.find(
(photoList) =>
photoList.id === event.target.value
);
const selectedOptions = YAML.stringify(
selectedPhotoTask.options,
null,
2
);
setFieldValue(
`photo.${index}.options`,
selectedOptions
);
}}
>
<option value="" defaultValue disabled>
...
</option>
{photoList &&
photoList.map((task) => (
<option key={task.id} value={task.id}>
{task.name}
</option>
))}
</Field>
</div>
</div>
</div>
<div className="row">
<div className="col-12">
<div className="form-group">
<label htmlFor={`photo.${index}.options`}>
OPTIONS
</label>
<Field
id="options"
component="textarea"
className="form-control"
name={`photo.${index}.options`}
type="text"
rows="4"
onChange={handleChange}
/>
</div>
</div>
</div>
</div>
<div className="col-1">
<button
type="button"
className="btn"
onClick={() => remove(index)}
>
DELETE
</button>
</div>
</div>
))}
<button
type="button"
className="btn"
onClick={() =>
push({
id: '',
options: '',
// options: JSON.stringify('', null, 0),
})
}
>
ADD
</button>
</>
)}
</FieldArray>
<button className="btn" type="submit" disabled={isSubmitting}>
SUBMIT
</button>
</Form>
)}
</Formik>
</div>
);
};
export default NewPhoto;
I needed to save the values into their own variable, and then loop through them and apply a YAML.parse() for each index.
let submitVals = values;
for (let index = 0; index < submitVals.photo.length; index++) {
let optionsText = YAML.parse(submitVals.photo[index].options);
submitVals.photo[index].options = optionsText;
}
console.log(submitVals);
send(submitVals);

How to design State for Multiple objects using react-redux?

I need to create multiple medicine objects here. I just want to change state into array of objects. How to do that effectively? Also, want to implement controlled component for multiple medicine objects form.
Here's my component for a single medicine object:
export class MedicineForm extends Component {
state = {
medicine_name: "",
details: ""
}
static propTypes = {
postMedicine: PropTypes.func.isRequired
}
onChange = e => {
this.setState({
[e.target.name]: e.target.value
})
}
onSubmit = e => {
e.preventDefault()
const { medicine_name, details } = this.state
const medicine = { medicine_name, details }
this.props.postMedicine(medicine)
// Following code works as desired. Need to change state in this JSON Array of objects.
// this.props.postMedicine([
// {
// "id": 14,
// "medicine_name": "many5",
// "details": "sdknas"
// },
// {
// "id": 15,
// "medicine_name": "many6",
// "details": "sdknas"
// }
// ])
}
render() {
const { medicine_name, details } = this.state
return (
<Fragment>
<h1>Add Medicine</h1>
<form className="card card-body" onSubmit={this.onSubmit}>
<div className="form-row">
<div className="form-group col-md-3">
<label htmlFor="medicine_name">Medicine Name</label>
<input type="text" className="form-control" name="medicine_name" id="medicine_name" placeholder="Medicine Name" value={medicine_name} onChange={this.onChange} />
</div>
<div className="form-group col-md-3">
<label htmlFor="details">Details</label>
<input type="text" className="form-control" name="details" id="details" placeholder="Details" value={details} onChange={this.onChange} />
</div>
<div className="form-group mx-auto mt-3">
<button type="submit" className="btn btn-primary btn-lg">
Submit
</button>
</div>
</div>
</form>
</Fragment>
)
}
}
In actions, I have added following postMedicine method:
export const postMedicine = (medicine) => dispatch => {
axios.post('./api/medicine/', medicine)
.then(res => {
dispatch({
type: POST_MEDICINE,
payload: res.data
})
})
.catch(err => console.log(err))
}
//this is one row, add multiple rows as needed
state = {
medicines: [{medicine_name: "",
details: ""
}]
}
//other code
onChange = (e, i) => {
const newMedicines = this.state.medicines;
newMedicines[i] = {[e.target.name]: e.target.value, ...newMedicines[i]}
this.setState({medicines: newMedicines})
}
onSubmit = e => {
e.preventDefault()
const { medicine_name, details } = this.state
const medicine = { medicine_name, details }
this.props.postMedicine(medicine)
// Following code works as desired. Need to change state in this JSON Array of objects.
// this.props.postMedicine(this.state.medicines)
}
<form className="card card-body" onSubmit={this.onSubmit}>
{this.state.medicines.map((m, i) => (<div className="form-row">
<div className="form-group col-md-3">
<label htmlFor="medicine_name">Medicine Name</label>
<input type="text" className="form-control" name="medicine_name" id="medicine_name" placeholder="Medicine Name" value={m.medicine_name} onChange={(e) => this.onChange(e, i)} />
</div>
<div className="form-group col-md-3">
<label htmlFor="details">Details</label>
<input type="text" className="form-control" name="details" id="details" placeholder="Details" value={m.details} onChange={(e) => this.onChange(e, i)} />
</div>
<div className="form-group mx-auto mt-3">
<button type="submit" className="btn btn-primary btn-lg">
Submit
</button>
</div>
</div>))}
</form>
Form component has two parameter (aka props).
first one is item, wich determines how many form do you need.
1 form means you have group of two inputs [medicine_name,details]
2 = 4 input (2 group)
...etc
and second props is function named formHandler.
wich lifting data from child
export class MedicineForm extends Component {
state = {
medicine: [],
};
static propTypes = {
postMedicine: PropTypes.func.isRequired,
};
formHandler = (value) => {
this.setState({ medicine: value });
};
onSubmit = (e) => {
e.preventDefault();
this.props.postMedicine(this.medicine);
};
render() {
return (
<>
<h1>Add Medicine</h1>
{JSON.stringify(this.state.medicine)}
<form className="card card-body" onSubmit={this.onSubmit}>
<Form item="4" formHandler={this.formHandler} />
<div className="form-group mx-auto mt-3">
<button type="submit" className="btn btn-primary btn-lg">
Submit
</button>
</div>
</form>
</>
);
}
}
Form Component
class Form extends Component {
constructor(props) {
super(props);
}
state = {
medicine: [...Array(+this.props.item)].map((_, idx) => ({
id: idx + 1,
medicine_name: "",
details: "",
})),
};
static propTypes = {
item: PropTypes.string,
formHandler: PropTypes.func,
};
onChange = ({ target: { id, name, value } }) => {
this.setState((prevState) => {
const medicine = prevState.medicine.map((item) =>
item.id === Number(id) ? { ...item, [name]: value } : item
);
this.props.formHandler(
medicine.filter((item) => item["medicine_name"] || item["details"])
);
return {
medicine,
};
});
};
render() {
return (
<div className="form-row">
{this.state.medicine.map((item, id) => (
<div key={item.id}>
<div className="form-group col-md-3">
<label htmlFor="medicine_name">Medicine Name</label>
<input
type="text"
className="form-control"
name="medicine_name"
id={item.id}
value={item.medicine_name}
placeholder="Medicine Name"
onChange={this.onChange}
/>
</div>
<div className="form-group col-md-3">
<label htmlFor="details">Details</label>
<input
type="text"
className="form-control"
name="details"
id={item.id}
value={item.details}
placeholder="Details"
onChange={this.onChange}
/>
</div>
</div>
))}
</div>
);
}
}
there is check if object has some value then lifting data.
you can change logic optional
medicine.filter((item) => item["medicine_name"] || item["details"])
You could do something like this in redux store:
[
{ id: 1, medicineName: '', details: '' },
{ id: 2, medicineName: '', details: '' },
...
]
And to make your input fields controlled just handle the state in the component.

React component is not re-rendered after the state is changed with a dropdown list [react hooks]

I have the following React component (using hooks), which lists a number of Tasks as a dropdown list. When an item is selected from the list, I want to display an Update form. This works only when an item is selected for the first time. When I select a new item, nothing happens (although console.log(e.target.value); prints the correct value). I store the selected task's id in st_taskId.
I wonder if you see any issues in the code below:
const ManageReviewTasks = props => {
const reviewRoundId = props.match.params.reviewRoundId;
const [st_taskId, set_taskId] = useState();
useEffect(() => {
if (props.loading == false && st_taskId == null)
props.fetchReviewTasksByReviewRound(reviewRoundId);
}, [reviewRoundId, st_taskId]);
if (props.loading == true) {
return <div>Loading...</div>;
}
return (
<>
{props.reviewTasks && (
<div>
<h3>Configure the Review Tasks</h3>
<br />
{
<div>
<div>
<h4>
Tasks for <span className="font-italic">students receiving</span> feedback:
</h4>
<select
className="form-control"
onChange={e => {
e.preventDefault();
console.log(e.target.value);
set_taskId(e.target.value);
}}>
<option>--SELECT--</option>
{Object.keys(props.reviewTasks).map(id => {
const task = props.reviewTasks[id];
{
if (task.isForStudent) {
return (
<option key={id} id={id} value={id}>
{task.title}
</option>
);
}
}
})}
</select>
</div>
{props.reviewTasks[st_taskId] && (
<UpdateReviewTaskForm task={props.reviewTasks[st_taskId]} />
)}
</div>
}
</div>
)}
</>
);
};
Below is the code for the UpdateReviewTaskForm component:
const UpdateReviewTaskForm = (props) => {
const [st_Title, set_Title] = useState(props.task.title);
const [st_Description, set_Description] = useState(RichTextEditor.createValueFromString(props.task.description, 'html'));
const [st_startDate, set_startDate] = useState(new Date(props.task.startDate.replace('-', '/')));
const [st_DueDate, set_DueDate] = useState(new Date(props.task.dueDate.replace('-', '/')));
const handleCancelClick = (event) => {
event.preventDefault();
history.goBack();
}
const onSubmit_saveTask = (e) => {
e.preventDefault();
props.updateReviewTask({
Id: props.task.id,
Title: st_Title,
Description: st_Description.toString('html'),
StartDate: format(st_startDate, 'DD/MM/YYYY'),
DueDate: format(st_DueDate, 'DD/MM/YYYY'),
})
}
if (props.loading)
return <div>Updating...</div>
return (
<div>
<br/>
<br/>
<div className="p-3 bg-light">
<h3 className="text-info">Update the Task:</h3>
{
props.task &&
<form onSubmit={onSubmit_saveTask}>
<div className="form-group">
<label>Enter the title</label>
<input
//placeholder="Enter a title..."
value={st_Title}
onChange={(event) => { set_Title(event.target.value) }}
className="form-control" />
</div>
<div className="form-group">
<label>Enter a description for the assessment</label>
<RichTextEditor
value={st_Description}
onChange={set_Description}
/>
</div>
<div className="form-group">
<label>Start date to start: </label>
<DatePicker
className="form-control"
selected={st_startDate}
onChange={(date) => set_startDate(date)}
/>
</div>
<div className="form-group">
<label>Due date to complete: </label>
<DatePicker
className="form-control"
selected={st_DueDate}
onChange={(date) => set_DueDate(date)}
/>
</div>
<br />
<button type="submit" className="btn btn-primary">Submit</button>
<button type="reset" className="btn btn-light" onClick={handleCancelClick}>Cancel</button>
</form>
}
</div>
</div>
)
}
Because you are using internal state in UpdateReviewTaskForm, even if this component re-render for the second time, its state will not be reset (to the default value props.task.title for example).
One way to force the state to reset is to use a key prop in UpdateReviewTaskForm like this :
{props.reviewTasks[st_taskId] && (
<UpdateReviewTaskForm key={st_taskId} task={props.reviewTasks[st_taskId]} />
)}
Another way is to use useEffect inside UpdateReviewTaskForm to run when props.task change
useEffect(() => {
// reset the state here
}, [props.task])

Final form array fields async validation

I am trying to implement async validation react-final-form-array. But its returning promise but not showing error at all.
I took reference of this code for asnyc validate and created my own version with final form array here.
Form:
<Form
onSubmit={onSubmit}
mutators={{
...arrayMutators
}}
initialValues={{ customers: [{ placeholder: null }] }}
validate={formValidate}
render={({
handleSubmit,
mutators: { push, pop }, // injected from final-form-arrays above
pristine,
reset,
submitting,
values,
errors
}) => {
return (
<form onSubmit={handleSubmit}>
<div>
<label>Company</label>
<Field name="company" component="input" />
</div>
<div className="buttons">
<button type="button" onClick={() => push("customers", {})}>
Add Customer
</button>
<button type="button" onClick={() => pop("customers")}>
Remove Customer
</button>
</div>
<FieldArray name="customers" validate={nameValidate}>
{({ fields }) =>
fields.map((name, index) => (
<div key={name}>
<label>Cust. #{index + 1}</label>
<Field
name={`${name}.firstName`}
component={testInput}
placeholder="First Name"
/>
<Field
name={`${name}.lastName`}
component={testInput}
placeholder="Last Name"
/>
<span
onClick={() => fields.remove(index)}
style={{ cursor: "pointer" }}
>
❌
</span>
</div>
))
}
</FieldArray>
<div className="buttons">
<button type="submit" disabled={submitting || pristine}>
Submit
</button>
<button
type="button"
onClick={reset}
disabled={submitting || pristine}
>
Reset
</button>
</div>
<pre>
nameValidate Function:{"\n"}
{JSON.stringify(nameValidate(values.customers), 0, 2)}
</pre>
<pre>
Form errors:{"\n"}
{JSON.stringify(errors, 0, 2)}
</pre>
<pre>
Form values:{"\n"}
{JSON.stringify(values, 0, 2)}
</pre>
</form>
);
}}
/>
Validate Function:
const duplicateName = async value => {
await sleep(400);
if (
~["john", "paul", "george", "ringo"].indexOf(value && value.toLowerCase())
) {
return { firstName: "name taken!" };
}
};
const nameValidate = values => {
if (!values.length) return;
const errorsArray = [];
values.forEach(value => {
if (value) {
let errors = {};
if (!value.firstName) errors.firstName = "First Name Required";
if (Object.keys(errors).length === 0) {
errors = duplicateName(value.firstName);
console.log("errors", errors);
}
errorsArray.push(errors);
}
});
return errorsArray;
};
Okay, you're mixing the validation in a way that's not allowed. You can't embed a Promise inside an array of errors, the whole validation function must return a promise. Here's the fixed version. The await duplicateName() is the important bit.
const nameValidate = async values => {
if (!values.length) return
return await Promise.all(
values.map(async value => {
let errors = {}
if (value) {
if (!value.firstName) errors.firstName = 'First Name Required'
if (Object.keys(errors).length === 0) {
errors = await duplicateName(value.firstName)
}
}
console.error(errors)
return errors
})
)
}

Resources