React Dynamic inputs with Formik - javascript

I am trying to dynamically remove an input field after a button is pressed and also remove the key with its value from Formik. I am using the useFormik hook for implementation. The problem is that when I press the button to remove the input field, it is removed but the key and value stay in useFormik. When I press the button again, another input field is removed and the previous key and value is removed from useFormik. Removing values from useFormik is one step behind. How can I change it so it removes the key and value at the same time as the input field?
Here are initial values for Formik.
const formik = useFormik({
initialValues: {
Produkt1:
"",
Produkt2:
"",
},
validationSchema: frontProductUrls(),
enableReinitialize: true,
onSubmit: (values) => {
console.log(values);
},
setFieldValue: () => {
delete fields.length - 1;
},
});
This array i use to dynamicly add remove and render input fields.
const [fields, setFields] = useState([
{ name: "Produkt1", label: "Produkt 1" },
{ name: "Produkt2", label: "Produkt 2" },
]);
Here is function to remove last input filed in array and also remove key and value from useFormik.
const removeField = (e) => {
formik.setFieldValue(`Produkt${fields.length + 1}`, undefined, false);
let updatedFields = [...fields];
updatedFields.splice(fields.length - 1, 1);
setFields(() => updatedFields);
};
This function is used to add new input field. This work fine it add new input and also it add new key in useFormik.
const addField = () => {
if (fields.length > 3) return;
const newProduct = `Produkt${fields.length + 1}`;
formik.setFieldValue(newProduct, "");
setFields([
...fields,
{
name: newProduct,
label: newProduct,
},
]);
};
Here is code where are fields rendered.
{fields.map((field, index) => {
return (
<div key={index} className="mb-4">
<label
htmlFor={field.name}
className="block text-gray-700 font-medium mb-2"
>
{field.label}
</label>
<input
id={field.name}
name={field.name}
type="text"
onChange={formik.handleChange}
value={formik.values[field.name]}
className="border border-gray-400 p-2 rounded-lg w-full"
/>
</div>
);
})}
```

I had to add this code
formik.setFieldValue(`Produkt${fields.length}`, "");
setFields(fields.filter((_, i) => i !== fields.length - 1));
useEffect(() => {
formik.
formik.setValues({ fields });
}, [fields]);

Related

This filter method is working in console but not in ui in react

I want to filter items by filter method and I did it but it doesn't work in UI but
when I log it inside console it's working properly
I don't know where is the problem I put 2 images
Explanation of this code:
Looping inside currencies_info by map method and show them when I click on it and this completely working then I want filter the list when user enter input inside it I use filter method and this completely working in console not in UI
import React, { useState } from "react";
// Artificial object about currencies information
let currencies_info = [
{
id: 1,
name: "بیت کوین (bitcoin)",
icon: "images/crypto-logos/bitcoin.png",
world_price: 39309.13,
website_price: "3000",
balance: 0,
in_tomans: 0
},
{
id: 2,
name: "اتریوم (ethereum)",
icon: "images/crypto-logos/ethereum.png",
world_price: 39309.13,
website_price: "90",
balance: 0,
in_tomans: 0
},
{
id: 3,
name: "تتر (tether)",
icon: "images/crypto-logos/tether.png",
world_price: 39309.13,
website_price: "5",
balance: 0,
in_tomans: 0
},
{
id: 4,
name: "دوج کوین (dogecoin)",
icon: "images/crypto-logos/dogecoin.png",
world_price: 39309.13,
website_price: "1000000",
balance: 0,
in_tomans: 0
},
{
id: 5,
name: "ریپل (ripple)",
icon: "images/crypto-logos/xrp.png",
world_price: 39309.13,
website_price: "1,108",
balance: 0,
in_tomans: 0
}
];
export default function Buy() {
// States
let [api_data, set_api_data] = useState(currencies_info);
const [currency_icon, set_currency_icon] = useState("");
const [currency_name, set_currency_name] = useState("");
const [currency_price, set_currency_price] = useState(0);
const [dropdown, set_drop_down] = useState(false);
let [search_filter, set_search_filter] = useState("");
// States functions
// this function just toggle dropdown list
const toggle_dropdown = () => {
dropdown ? set_drop_down(false) : set_drop_down(true);
};
// this function shows all currencies inside dropdown list and when click on each item replace
// the currency info and hide dropdown list
const fetch_currency = (e) => {
set_drop_down(false);
currencies_info.map((currency) => {
if (e.target.id == currency.id) {
set_currency_name(currency.name);
set_currency_icon(currency.icon);
set_currency_price(currency.website_price);
}
});
};
// this function filter items base on user input value
const filter_currency = (e) => {
set_search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
api_data = set_search_filter;
console.log(api_data);
};
return (
<div className="buy-page-input" onClick={toggle_dropdown}>
{/* currency logo */}
<div className="currency-logo">
<img src={currency_icon} width="30px" />
</div>
{/* currency name in persian */}
<span className="currency-name">{currency_name}</span>
{/* currency dropdown icon */}
<div className="currency-dropdown">
<img className={dropdown ? "toggle-drop-down-icon" : ""}
src="https://img.icons8.com/ios-glyphs/30/000000/chevron-up.png"
/>
</div>
</div>
{/* Drop down list */}
{dropdown ? (
<div className="drop-down-list-container">
{/* Search box */}
<div className="search-box-container">
<input type="search" name="search-bar" id="search-bar"
placeholder="جستجو بر اساس اسم..."
onChange={(e) => {
filter_currency(e);
}}/>
</div>
{api_data.map((currency) => {
return (<div className="drop-down-list" onClick={(e) => {
fetch_currency(e);}} id={currency.id}>
<div class="right-side" id={currency.id}>
<img src={currency.icon} width="20px" id={currency.id} />
<span id={currency.id}>{currency.name}</span>
</div>
<div className="left-side" id={currency.id}>
<span id={currency.id}>قیمت خرید</span>
<span className="buy-price" id={currency.id}>
{currency.website_price}تومان</span>
</div>
</div>);})}
</div>) : ("")});}
Your search_filter looks redundant to me.
Try to change the filter_currency function like this:
const filter_currency = (e) => {
const search = e.target.value;
const filtered = currencies_info.filter((currency) => {
return currency.name.includes(search);
});
set_api_data(filtered);
};
It looks like you are never setting the api_data after you set the filter state.
Change the following
api_data = set_search_filter
console.log(api_data)
to
api_data = set_search_filter
set_api_data(api_data)
However, it then looks like set_search_filter is never used and only set so to improve this further you could remove that state and just have it set the api_data direct. Something like this:
const filter_currency = (e) => {
const search_filter = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1
})
set_api_data(search_filter)
}
Change your state value from string to array of the search_filter like this -
let [search_filter, set_search_filter] = useState([]);
and also it should be like this -
const filter_currency = (e) => {
const filterValues = currencies_info.filter((currency) => {
return currency.name.indexOf(e.target.value) !== -1;
});
set_search_filter(filtervalues);
set_api_data(filterValues);
console.log(filterValues);
};
and use useEffect with search_filter as dependency, so that every time search_filter value is being set, useEffect will trigger re render, for eg:-
useEffect(()=>{
//every time search_filter value will change it will update the dom.
},[search_filter])

MultiSelect with formState issue on form Submit

I am using MultiSelect inside a form and wanted to set the formState with the selected values using onChange.
At present, the below code passes the default null value for permission_sets on form submit.
Can someone take a look and let me know how to update the value of permission_sets inside formState from onChange on dropdown selections. This will pass the actual selected values on the form Submit.
const formState = {
permission_sets: "",
}
function updateFormState(key, value) {
formState[key] = value;
}
function App() {
const [permission_sets, set_permission_sets] = useState([]);
return (
<Form.Group>
<Form.Label>permission_sets</Form.Label>
<MultiSelect
options={[
{ label: "Operations", value: "Operations" },
{ label: "ReadOnlyAccess", value: "ReadOnlyAccess" },
{ label: "Dba", value: "Dba"},
]}
id="permission_sets"
labelledBy="Select"
value={permission_sets}
onChange={set_permission_sets}
/>
</Form.Group>
<Button onClick={validate}>Submit Request</Button>
)}

Can i populate a formik initialValue field that is an array? i'm using the react multiselect dropdown here, pls how can i do this

** const initialValues AssignClass: [],**
**NOW IN THE MULTISELECT DROP DOWN COMPONENT, i have set up formik there to my best effort, take a look **
import { ErrorMessage, useField } from "formik";
import Multiselect from "multiselect-react-dropdown";
const optionsArray = [
{ name: "Option1", id: 1 },
{ name: "Daniel", id: 2 },
];
const AppMultiSelect = ({
label = "Assign Class(es)",
obscure = false,
extraClasses,
error,
touched,
onFieldTouched,
options,
onSelect,
onRemove,
...props
}) => {
const [field, meta, helpers] = useField(props);
console.log(helpers, " the helpers");
return (
<div className="flex flex-col cursor-pointer w-full z-50 p-[5px]">
<label className="text-[14px] text-[#7D8592] font-semibold mb-2">
{label}
</label>
<div className=" w-full text-[#DADADA] py-[7px] border border-[#D8E0F0] rounded-2xl focus:border-danger focus:ring-danger py-[12px] px-[14px]">
<Multiselect
options={options} // Options to display in the dropdown
selectedValues={null} // Preselected value to persist in dropdown
onSelect={(selectedList) => {
helpers.setValue(...field.value, selectedList);
console.log(selectedList, " you selected this");
}} // Function will trigger on select event
onRemove={(selectedItem) =>
console.log(field.value, " to be removed")
} // Function will trigger on remove event
displayValue="name" // Property name to display in the dropdown options
{...props}
/>
</div>
{/* {error && touched && (
<span className="text-[14px] text-accent">{error}</span>
)} */}
</div>
);
};
export default AppMultiSelect;
** so, as you see above, i'm using the setValue, but then, the field updates but it happens once, even if i keep selecting more items... thanks in advance, by the way, im doing the formik validation and the rest in another component called addTeacher, and using yup for the schema part **
AppMultiSelect
name="AssignClass"
options={[
{ name: "fred" },
{ name: "cere" },
{ name: "veee" },
{ name: "mmeee" },
{ name: "typ" },
{ name: "wewe" },
]}
/>

Dynamically add and remove inputs and update nested array

How to add new input fields dynamically to object in nested array in react js when user clicks on plus sign? dynamically add and remove inputs
I want to add and delete propositionTimes dynamically in the handlepropositionTimeAddClick and handlepropositionTimeRemoveClick methods I shared below. How can I do that? And I want to do the same with propositionResponseTimes and analyzers.
const [issue, setIssue] = useState({
firstResponseDuration: "",
firstResponseOvertime: "",
solutionDuration: "",
solutionOvertime: "",
propositionTimes: [{
propositionTime: ""
}],
propositionResponseTimes: [{ propositionResponseTime: "" }],
analyzers: [{ analyzerName: "", analyzerHuaweiId: "" }],
});
const { firstResponseDuration, firstResponseOvertime,solutionDuration, solutionOvertime, propositionTimes, propositionResponseTimes, analyzers } = issue;
.
.
.
// handle click event of the Remove button
const handlepropositionTimeRemoveClick = index => {
};
// handle click event of the Add button
const handlepropositionTimeAddClick = (i) => {
};
.
.
.
{
issue.propositionTimes.map((item, i) => {
return (
<div key={i} className="form-group" >
<label>
Proposition Time
</label>
<TextField
type="datetime-local"
placeholder="Enter propositionTime"
name="propositionTime"
format="dd-MM-yyyy HH:mm"
value={item.propositionTime}
onChange={e => onInputPropositionTimes(e, i)}
/>
<div>
{issue.propositionTimes.length !== 1 && <button
className="mr10"
onClick={() => handlepropositionTimeRemoveClick(i)}>Remove</button>}
{issue.propositionTimes.length - 1 === i && <button onClick={handlepropositionTimeAddClick(i)}>Add</button>}
</div>
</div>
)
})
}
// handle click event of the Remove button
const handlepropositionTimeRemoveClick = index => {
const issueObj = {...issue};
const filteredIssue = issue.propositionTimes.filter((item, ind) => ind !== index);
issueObj.propositionTimes = filteredIssue;
setIssue(issueObj);
};
// handle click event of the Add button
const handlepropositionTimeAddClick = (i) => {
const issueObj = {...issue};
const newObj = {
propositionTime: "" // Code to add new propositionTime
}
issueObj.propositionTimes.push(newObj)
setIssue(issueObj);
};
Also in your handlepropositionTimeAddClick function, don't call it directly. Just pass the reference
{issue.propositionTimes.length - 1 === i && <button onClick={() => handlepropositionTimeAddClick(i)}>Add</button>}

How to add red borders around any invalid input index in mapped inputs

I have a modal which maps each element of the object newCompanies to a row:
{newCompanies.map((company, index) => {
return (
<div>
<div
className="side-by-side"
>
<ModalInput
type="text"
id="name"
value={company.name}
onChange={(e) =>
this.editCompanyArr(index, "name", e, "validName")
}
></ModalInput>
<ModalInput
type="text"
id="website"
value={company.website}
onChange={(e) =>
this.editCompanyArr(
index,
"website",
e,
"validWebsite"
)
}
></ModalInput>
<ModalInput
type="text"
id="sector"
value={company.class}
onChange={(e) =>
this.editCompanyArr(
index,
"sector",
e,
"validSector"
)
}
></ModalInput>
ModalInput is defined in styles.js:
export const ModalInput = styled.input`
padding: 10px;
margin: 10px 0;
border: 1px solid #f5f6f9;
border-radius: 20px;
background-color: #f5f6f9;
height: 100%;
width: ${props => props.width || 'auto'};
`;
And this is the function which checks whether a user entered a correct value. so for example, if the name contains numbers, the value of validName will become false:
editCompanyArr(i, obj, e, stateObject) {
const { newCompanies, validName } = this.state;
if (e.target.value.match("^[a-zA-Z ]*$") != null) {
this.setState({ [stateObject]: true });
} else {
this.setState({ [stateObject]: false });
}
//edited out more body to update value in newCompanies
}
How can I ensure that if a user enters an incorrect input for any mapped row, that only that particular input and not the rest of them get a red border?
I think the best approach to this is to add a CSS class that will give them the outline you want. Keep a flag for each input in the state, and set them to true on submit if it's value is invalid. Then you can clear the error when the user clicks into the input or types.
JSX:
const App = () => {
const [inputError, setInputError] = useState(false)
const refs = {
exampleInput: null
}
return (
<form onSubmit={e => {
const exampleInput = this.refs.exampleInput
const exampleInputVal = exampleInput.value
if(exampleInputVal) {
// It's valid! Submit it
} else {
// INVALID! Set error state:
setInputError(true)
}
}}>
<input
className={inputError ? 'error' : ''}
ref={el => { refs.exampleInput = el }}
onChange={e => setInputError(false)}
onFocus{e => setInputError(false)}
/>
</form>
)
}
CSS:
input.error,
input:focus.error {
outline: none;
box-shadow: 0 0 0 1px red;
}
You could try something like this:
Define the state variables you are going to need, I made a state variable for input values name and age (as an example). I also defined state for the classes so that when this value changes, the component re-renders and the border color updates.
I set some initial className values so that the first render is with a red border (border-danger in Bootstrap).
// initial classes
const initialClassNames = {
name: "border-danger",
age: "border-danger",
};
// needed states
const [classNames, setClassNames] = useState(initialClassNames);
const [values, setValues] = useState({ name: "", age: 0 });
Then I assigned the different fields with their own specifications that I want to map later. Note how the state values are bound to the input field specifications.
// specs for all input fields
const inputFields = [
{
inputLabel: "Name", // label for the input field
inputName: "name", // name
inputValue: values.name, // value
inputClassName: classNames.name // className
},
{
inputLabel: "Age",
inputName: "age",
inputValue: values.age,
inputClassName: classNames.age
}
];
In the return(), I map over all the fields and provide the wanted JSX.
return (
<div className="container mt-3">
{inputFields.map((f, i) => {
return (
<div className="mb-5" key={i}>
<label htmlFor={`${f.inputName}${i}`}>{f.inputLabel}</label>
<input
id={`${f.inputName}${i}`}
name={f.inputName}
value={f.inputValue}
onChange={handleChange}
className={`form-control ${f.inputClassName}`}
></input>
</div>
);
})}
</div>
);
This is how you would want to handle your change events for your inputs:
// handleChange for inputs
const handleChange = (e) => {
e.preventDefault();
setValues({
...values,
[e.target.name]: e.target.value
});
};
And you could do some validation like this:
useEffect(() => {
// validate name
if (values.name) {
setClassNames((prevState) => {
return { ...prevState, name: (prevState.name = "border-success") };
});
} else {
setClassNames((prevState) => {
return { ...prevState, name: (prevState.name = "border-danger") };
});
}
// validate age
if (parseInt(values.age, 10)) {
setClassNames((prevState) => {
return { ...prevState, age: (prevState.age = "border-success") };
});
} else {
setClassNames((prevState) => {
return { ...prevState, age: (prevState.age = "border-danger") };
});
}
}, [values.name, values.age]);
In the useEffect() function, I check if the input fields are correctly filled in every time the input values change (component re-renders). Depending on that, I change the class names to the wanted value. I used bootstrap classes (border-danger, border-success) in this example but you could do this with your own styling if you want.
If you only want the validation to happen on for example a button onClick(), this would also be possible.
codesandbox link

Categories