How to get value inside formik out using useEffect? - reactjs

my full code
https://codesandbox.io/s/testproducr02-bg7exz?file=/index.tsx
const [sum, setSum] = useState(0);
const [qty, setQty] = useState(0);
useEffect(() => {
const sum = values.product.reduce((acc, item) => acc + item.qty * 1, 0); //error values is not defined
setSum(sum);
});
useEffect is outside formik, but I don't know how to get the value out
this is inside my formik
<Formik
initialValues={initialValues}
onSubmit={async (values) => {
await new Promise((r) => setTimeout(r, 500));
//alert(JSON.stringify(values, null, 2));
// const sum = values.product.reduce(
// (acc, item) => acc + item.qty * 1,
// 0
// );
// setSum(sum);
}}
>
{({ values }) => (
<Form>
<FieldArray name="product">
{({ insert, remove, push }) => (
<div>
{values.product.length > 0 &&
values.product.map((friend, index) => (
<div className="row" key={index}>
<div className="col">
<label htmlFor={`product.${index}.productname`}>
Productname
</label>
<Field
name={`product.${index}.productname`}
placeholder="productname"
type="text"
/>
</div>
<div className="col">
<label htmlFor={`product.${index}.qty`}>Qty</label>
<Field
name={`product.${index}.qty`}
placeholder="qty"
type="number"
/>
</div>
<div className="col">
<button
type="button"
className="secondary"
onClick={() => remove(index)}
style={{ marginTop: "10px" }}
>
Delete
</button>
</div>
</div>
))}
<label>Sum</label>
<Field
name={`sum`}
placeholder="sum"
type="text"
value={sum}
disabled
/>
<br />
<button
type="button"
className="secondary"
onClick={() =>
push({ productname: "", qty: 0, price: 0, amount: 0 })
}
>
Add Product
</button>
</div>
)}
</FieldArray>
<p>Qty : {qty}</p>
<p>sum : {sum}</p>
<button type="submit">Summit</button>
</Form>
)}
</Formik>
i want to calculate it without pressing onSubmit

You can use Formik's innerRef prop to attach a ref to the Formik component.
// ...
const formikRef = useRef(null);
// ...
return (
<Formik
initialValues={initialValues}
innerRef={formikRef}
// ...
>
...
</Formik>
);
And then in some useEffect
if (formikRef.current) {
const productData = formikRef.current.values.product
// do something
}
I believe there are other methods too, such as "Formik Context"

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!

Opening all modals of a table at once react typescript

In an object array table, made by map, I need a delete modal, the problem is that when I click to open a single modal they all open from all the rows of the table as if I had clicked on all the buttons in the table not letting me select the right modal, I can't use this because of how I'm assembling the component. (I summarized the code but involving the button has a map running)
const TableAppointments = () => {
const [modalOpened, setModalOpened] = useState(false);
const [tableValues, setTableValues] = useState(tableAppointmentsMock);
const tasks = tableValues;
const setField = (newValue: any) => {
setTableValues((oldFormData: any) => ({ ...oldFormData, ...newValue }));
};
const handleDeleteTask = (id: any) => {
let arr = tableValues.filter((value) => {
return value.project.includes(id);
});
console.log(arr);
};
return (
<div className="gridContainer">
{tasks.length > 0 ? (
<div className="sectionContainer">
<section className="headerContent">
<div className="header headerProject">Projeto</div>
<div className="header headerTask">Tarefa</div>
<div className="header headerStart">Início</div>
<div className="header headerEnd">Fim</div>
<div className="header headertotal">Total</div>
<div className="header headerBlank"></div>
</section>
{tasks.map((task, index) => {
return (
<section className="taskContainer" key={task.id}>
<div className="tagContent">
<p className="tagClient">Cliente</p>
</div>
<input
className="task taskDescription"
placeholder="Insira uma tarefa"
value={task.tasks}
name="task"
// onBlur={handleChangeInput}
onChange={(e: any) => setField({ tasks: e.target.value })}
/>
<input
className="task startTask timer"
placeholder="--:--"
value={task.startTime}
name="start"
// onBlur={handleChangeInput}
onChange={(e: any) => setField({ startTime: e.target.value })}
/>
<input
className="task endTask timer"
placeholder="--:--"
value={task.stopTime}
name="stop"
// onBlur={handleChangeInput}
onChange={(e: any) => setField({ stopTime: e.target.value })}
/>
<input
className="task totalTask timer"
placeholder="--:--"
value={task.totalTime}
name="total"
// onBlur={handleChangeInput}
onChange={(e: any) => setField({ totalTime: e.target.value })}
/>
<section className="task taskIcons">
<button
className="btIcon"
onClick={() => {
setModalOpened(true);
}}
>
excluir
</button>
{modalOpened && (
<ModalDelete
id={task.id}
onClick={() => handleDeleteTask(task.project)}
onClose={() => setModalOpened(false)}
/>
)}
</section>
</section>
);
})}
</div>
) : (
<div className="noAppointments">
<p className="noAppointmentsText">
Você não possui apontamentos nesse dia, aponte suas horas clicando
no botão "Novo Apontamento"
</p>
</div>
)}
</div>
);
};

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);

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