Set unique key for useState with multiple inputs - reactjs

I have an App with 2 inputs where you can choose 1 muscle and a number of exercices for this muscle.
I added an "Add" button to duplicates these inputs to add a new muscle to work with its number of exercices.
The problem is, when I change an input, all same inputs will change because they have the same key (I guess).
Anyone can show me how to solve this ?
Here is my code :
import { Formik, Form, Field } from "formik";
import React, { useState } from "react";
import { useNavigate } from "react-router-dom";
import { getAllMuscles, numberOfExercices } from "../helpers/dataModuler";
import { setInLocalStorage } from "../helpers/localStorageHandler";
export const ProgramForm: React.FC<{}> = () => {
const [numberOfMusclesInProgram, setNumberOfMusclesInProgram] = useState(1);
const navigate = useNavigate();
const listOfMuscles = getAllMuscles();
const initialValues = {
numberOfExercices: 0,
muscle: listOfMuscles[0]
};
const handleSubmit = (values: {}) => {
setInLocalStorage("program", values);
navigate("/programme");
};
return (
<>
<Formik
initialValues={initialValues}
onSubmit={(values) => handleSubmit(values)}
>
<Form>
<div id="container">
{[...Array(numberOfMusclesInProgram)].map((n, i) => (
<DisplayMuscleInProgram key={n} />
))}
</div>
<button
onClick={() =>
setNumberOfMusclesInProgram(numberOfMusclesInProgram + 1)
}
type="button"
className="rounded-md border"
>
Add
</button>
<div className="text-center">
<button
type="submit"
className="mt-8 rounded-lg bg-primary p-2 text-white"
>
Let's gooooo 💪
</button>
</div>
</Form>
</Formik>
</>
);
};
export const DisplayMuscleInProgram = (props: { key: any }) => {
const listOfMuscles = getAllMuscles();
return (
<>
<div className="my-4 grid grid-cols-5 justify-between gap-5">
<Field
as="select"
className="col-span-3 rounded-lg bg-lightGray p-3"
name={`muscle-${props.key}`}
>
{listOfMuscles.map((muscle, key) => (
<option key={key}>{muscle}</option>
))}
</Field>
<Field
as="select"
className="col-span-2 rounded-lg bg-lightGray p-3"
name="numberOfExercices"
>
{numberOfExercices(10)}
</Field>
</div>
</>
);
};

In ProgramForm replace <div id="container" /> with the following (replacing key={n} with key={i}):
<div id="container">
{[...Array(numberOfMusclesInProgram)].map((n, i) => (
<DisplayMuscleInProgram key={i} />
))}
</div>
Alternatively, you may try:
<div id="container">
{[...Array(numberOfMusclesInProgram).keys()].map((n) => (
<DisplayMuscleInProgram key={n} />
))}
</div>
Source: https://stackoverflow.com/a/33352604/975164
Currently, all your inputs have exactly the same name because of the following lines in DisplayMuscleInProgram:
<Field
as="select"
className="col-span-3 rounded-lg bg-lightGray p-3"
name={`muscle-${props.key}`} // this line
>
React docs recommend using someEntity.id as keys and warn against using indexes as keys when the order of list items is expected to change. However, in this case, I believe you will be safe.
As a side note: Since you are using Formik, I recommend using Formik's built-in <FieldArray /> for this purpose i.e. add more inputs to the form on button click. You may read more about it here: https://formik.org/docs/api/fieldarray

Related

adding specefic data from in array without loop

i want get all the data from dataFromes and put it in Modal.title but what i did using map it only show the first in array of dataFromes
import React, { useState } from 'react';
import Button from 'react-bootstrap/Button';
import Modal from 'react-bootstrap/Modal';
import Form from 'react-bootstrap/Form';
import { useSelector } from 'react-redux';
function ViewPop(props) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const dataFromes = useSelector(state => state.data.value);
const expenseForms = useSelector(state => state.expend.value);
return (
<div>
<Button variant= {props.outline} onClick={() => {
handleShow();
}} className= "mx-1">
{props.text}
</Button>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<div className='title flex'>
{dataFromes.map((data, index) => (
<Modal.Title>Expenses - {data.text}</Modal.Title>
))}
<Button variant="outline-danger" className='ml-2'>Delete</Button>
</div>
</Modal.Header>
<Form className="mx-3 mt-3">
<Form.Group className="mb-3">
{expenseForms ? expenseForms.map(expense => (
<div key={expense.text} className='expense flex justify-between items-center mb-3'>
<span className='fs-4'>{expense.text}</span>
<span className='fs-5'><span>$</span>{expense.amount} <Button className= "text-center mb-[9px] w-7 ml-1" size="sm" variant="outline-danger"><span className="text-[10px]">X</span></Button></span>
</div>
)) : null}
</Form.Group>
</Form>
{/* <Modal.Title>Expenses - {data.text}</Modal.Title> */}
</Modal>
</div>
)
};
export default ViewPop;
hello i want get all the data from dataFromes and put it in Modal.title but what i did using map it only show the first in array of dataFromes

react select mult with formik, the value does not appear in the array

Hello I have a web application, and I need to put a multi select in a modal, on the front it appears as it should, but I can't return its value in the input correctly as an array, in case anyone can help me. I'm using formik to manage submission in submit
the value of the second field works as expected, I've tried using useState to insert the value in the array but it didn't work either
Example of how values ​​are returned:
alert values
example of how values ​​are expected
example
import clsx from 'clsx'
import Axios from "axios";
import React, { useState } from "react";
import 'react-notifications-component/dist/theme.css'
import { Store } from 'react-notifications-component';
import { useFormik, FieldProps, FieldArray } from 'formik';
import Select , { OptionsType , ValueType } from "react-select" ;
import { Options, OnChangeValue } from "react-select";
const Modal_cadastra = ({close}) => {
const cities = [
{label: 'New York1', value: 'NY'},
{label: 'Rome', value: 'RM'},
{label: 'London', value: 'LDN'},
{label: 'Istanbul', value: 'IST'},
{label: 'Paris', value: 'PRS'}
];
const formik = useFormik({
initialValues: {
atividades: {
atividades_id:[],
},
descricao: '',
},
onSubmit: values => {
alert(JSON.stringify(values, null, 2));
},
});
return (
<>
<form id='kt_modal_add_user_form' onSubmit={formik.handleSubmit} className='form'noValidate>
<div
className='modal fade show d-block modal-open'
id='kt_modal_add_user'
role='dialog'
tabIndex={-1}
aria-modal='true'
>
{/* begin::Modal dialog */}
<div className='modal-dialog modal-dialog-centered mw-650px'>
{/* begin::Modal content */}
<div className='modal-content'>
<div className='modal-header'>
{/* begin::Modal title */}
<h2 className='fw-bolder'>Cadastrar tipo mão de obra</h2>
<div
className='btn btn-icon btn-sm btn-active-icon-primary'
data-kt-users-modal-action='close'
style={{cursor: 'pointer'}}
>
</div>
</div>
<div className='modal-body scroll-y mx-5 mx-xl-15 my-7'>
<div className='col-lg-10 fv-row'>
<Select
defaultValue={formik.values.atividades.atividades_id}
onBlur={formik.handleChange}
isMulti
name="atividades_id"
id="atividades_id"
options={cities}
className="basic-multi-select"
classNamePrefix="select"
/>
</div>
<div className='fv-row mb-7'>
<label className='fw-bold fs-6 mb-2'>TIPO MÃO DE OBRA</label>
<input
placeholder='Tipo mão de obra'
type='text'
name='descricao'
id='descricao'
className={clsx(
'form-control form-control-solid mb-3 mb-lg-0',
)}
autoComplete='off'
disabled={false}
onChange={formik.handleChange}
value={formik.values.descricao}
/>
</div>
</div>
<div className='fv-row mb-7'>
<div className='text-center'>
<button
type='reset'
className='btn btn-light me-3'
data-kt-users-modal-action='cancel'
onClick={ close }
>
Sair
</button>
<button
type='submit'
className='btn btn-primary'
data-kt-users-modal-action='submit'
>
<span className='indicator-label'>Salvar</span>
</button>
</div>
</div>
</div>
</div>
</div>
<div className='modal-backdrop fade show'></div>
</form>
</>
)
}
export {Modal_cadastra}
This is because Formik checks the actual rendered HTML tag for the name and react-select is not passing the name to the HTML element. It ignores it and uses its own.
Generally you need to bind third party components. You could split this out and then use it in multiple places:
<Field name="atividades_id">
{({ field, form, meta }) => (
<Select
{...field}
isMulti
id="atividades_id"
onChange={(values) => form.setFieldValue("atividades_id", values)} {/* Because formiks field.onChange` accepts an event, we need to manually bind this one as Select passes up the new values not nested in event */}
getOptionValue={option => cities.value}
options={cities}
className="basic-multi-select"
classNamePrefix="select"
/>
)}
</Field>

Input field not cleared after using useState with onClick

I have a React app, where I'm using an input field as a searchbar, which upon typing, returns a list of products, and upon clicking any product takes you to that product page. I want to clear the typed text in the searchbar after the redirection to new page has happened but I haven't been able to achieve this yet.
I've tried many methods and went over similar posts, but I don't know what am I doing wrong as text is never cleared.
I'm using Material UI for rendering the list and have imported everything as needed.
Below is my code:
Navbar component (contains searchbar)
const Navbar = () => {
const [text, setText] = useState('');
const [liopen, setLiopen] = useState(true);
const getText = (text) => {
setText(text);
setLiopen(false);
};
const handleClick2 = (e) => {
setText('');
setLiopen(true)
};
return (
<header>
<nav>
<div className="middle">
<div className="nav_searchbar">
<span className="search_icon">
<SearchIcon id="search" />
</span>
<input
type="text"
onChange={(e) => getText(e.target.value)}
name=""
placeholder="Search for products, categories, ..."
id=""
/>
</div>
{text && (
<List className="extrasearch" hidden={liopen}>
{products
.filter((product) =>
product.title.toLowerCase().includes(text.toLowerCase())
)
.map((product) => (
<ListItem>
<NavLink
to={`/getproductsone/${product.id}`}
onClick={(e) => {handleClick2(e)}}
>
{product.title}
</NavLink>
</ListItem>
))}
</List>
)}
</nav>
</div>
</header>
);
};
export default Navbar;
You need to set the value of the input if you want it controlled by the component state.
<input value={text}
type="text"
onChange={(e) => getText(e.target.value)}
name=""
placeholder="Search for products, categories, ..."
id=""
/>

Dynamic add Image and data with Ant-Design and ReactJs

I can't add an image while executing. When adding an image, the image does not appear, I use a dynamic form with the same inputs when adding a form which will later be stored by looping, but I have problems when adding images. I use a plugin from ant design.
Here's the code.
import React,{useState} from 'react'
import { Button,Form,Input } from 'antd';
import {
PlusOutlined,
MinusCircleOutlined,
} from '#ant-design/icons';
function Ad_abhb() {
const [Img1,setImg] = useState([]);
const i = 0;
const AddImg4 = (i) =>{
const [imgPrev1,setImgPrev1] = useState('');
const ImgC1=(e)=>{
const reader = new FileReader();
const vImg = e.target.files[0];
setImgPrev1(URL.createObjectURL(vImg));
reader.readAsDataURL(vImg);
reader.onload = () => {[...setImg(reader.result)]}
}
return(<>
<Form.Item className='imgHobi'>
<Input id={'uImg4'+i.kd} type="file" accept=".jpg,.jpeg,.png" onChange={ImgC1} hidden />
<label htmlFor={'uImg4'+i.kd}>
{imgPrev1.length >= 1 ?<>
<div className='imgLoad'>
<img src={imgPrev1} className='getImg' alt=''/>
</div>
</>:<>
<div className='imgLoad'>
<div className='UpImg'>
<div className='UpCore'>
<PlusOutlined style={{fontSize:'20px'}}/>
</div>
</div>
</div>
</>}
</label>
</Form.Item>
</>)
}
const SimpanA4=()=>{
console.log(Img1)
}
return (<>
<h1>hobi</h1>
<div className='konten'>
<Form >
<Form.List name='Hobi'>
{(fields, { add, remove }) => (<>
{fields.map((field,i)=>{
return(
<div key={field.key} className='cardAdminMod hobiList' >
<AddImg4 kd={i}/>
<Form.Item className='inputHobi' name="Hobi" {...field}>
<Input />
</Form.Item>
{fields.length > 1 ? <div className='rmHobi'><MinusCircleOutlined onClick={() => remove(field.name)} /></div>: null}
</div>)
})}
<Form.Item className='addPlusHobi'>
<Button type="dashed" onClick={() => add()} block icon={<PlusOutlined />}>
Add Data
</Button>
</Form.Item>
</>)}
</Form.List>
</Form>
</div>
<div className="titleAdmin butSide">
<Button onClick={SimpanA4}>Save</Button>
</div>
</>)
}
export default Ad_abhb;
So, can anyone help me?

Grabbing the Value of a Dynamically Created Object

I am dynamically creating buttons from an API call that looks like this:
My goal is that when a button is clicked the inner text will display in the search bar above.
below is the code for this auto complete component:
const Autocomplete = (props) =>
{
const btnSearch = (e) => {
console.log(props.suggestions)
}
return(
<>
{props.suggestions.map((e) => (
<button className={style.btn} onClick={btnSearch} key={e}>{e}</button>
))}
</>
);
}
export default Autocomplete;
The Autocomplete component is then being placed in a div as seen here:
return (
<>
<div className={style.container}>
<div className={style.title_hold}>
<h1>turn one</h1>
<h2>a search engine and game companion for Magic: The Gathering</h2>
</div>
<input className={style.search} type='text' placeholder='search for cards here...' value={search} onChange={handleInputChange} onKeyPress={handleSubmit}></input>
<div className={style.btn_wrap}>
<Autocomplete suggestions={results} />
</div>
<div className={style.data_wrap} id='user_display'>
<div className={style.img_wrap}>
{photos}
</div>
<div className={style.display_info}>
<h2>{card.name}</h2>
<h2>{card.type_line}</h2>
<h2>{card.oracle_text}</h2>
</div>
</div>
</div>
</>
)
Any help would be appreciated.
You can create a state variable in your parent component and then pass a function to the Autocomplete's button for the onClick which will then update the state in the parent. Something like this:
const Autocomplete = (props) => {
return(
<>
{props.suggestions.map((e) => (
<button className={style.btn} onClick={() => props.setSearch(e)} key={e}>{e}</button>
))}
</>
);
}
export default Autocomplete;
Your parent component:
import React from 'react'
const ParentComponent = (props) => {
const [searchText, setSearchText] = React.useState("")
const handleClick = (textFromButtonClick) => {
setSearchText(textFromButtonClick)
}
return (
<>
<div className={style.container}>
<div className={style.title_hold}>
<h1>turn one</h1>
<h2>a search engine and game companion for Magic: The Gathering</h2>
</div>
<input className={style.search} type='text' placeholder='search for cards here...' value={searchText} onChange={handleInputChange} onKeyPress={handleSubmit}></input>
<div className={style.btn_wrap}>
<Autocomplete setSearch={handleClick} suggestions={results} />
</div>
<div className={style.data_wrap} id='user_display'>
<div className={style.img_wrap}>
{photos}
</div>
<div className={style.display_info}>
<h2>{card.name}</h2>
<h2>{card.type_line}</h2>
<h2>{card.oracle_text}</h2>
</div>
</div>
</div>
</>
)
}
export default ParentComponent;
I took over your input value in the parent component, but without seeing any of your other code, I have no idea how you're managing that but you can likely merge it into this workflow to wire it up.

Resources