React: Select component not updating inside a dynamic form - reactjs

I am attempting to create a dynamic form in which there are 2 text fields and one dropdown select. These fields can be added by clicking the "Add More.." button. The remove button removes a particular field set. After an npm start the code shows all elements normally, add, remove and input fields work as intended. However, the problem starts when the select is used. On selecting something, the app crashes and gives a white screen with the errors [tag:"formFields.map is not a function"] and [tag:"Consider adding an error boundary to your tree to customize error handling behavior."] I would appreciate any help that can resolve this. :)
P.S. I am learning react through building projects rather than the conventional method of sitting through hours of tutorials and figuring things out. I am grateful to any help that is offered to me.
import { useState } from "react";
function FoodPreferences(){
const [formFields, setFormFields] = useState([
{ name: '', age: '', food: '' }
])
const [foodState, setFoodState] = useState("dumpling");
const handleFormChange = (event, index) => {
let data = [...formFields];
data[index][event.target.name] = event.target.value;
setFormFields(data);
}
const handleSelectChange = (event, index) => {
const selectedFood = event.target.value
setFormFields(selectedFood)
}
const submit = (e) => {
e.preventDefault();
console.log(formFields, foodState)
}
const addFields = () => {
let object = {
name: '',
age: '',
food: ''
}
setFormFields([...formFields, object])
}
const removeFields = (index) => {
let data = [...formFields];
data.splice(index, 1)
setFormFields(data)
}
return (
<div className="App">
<form onSubmit={submit}>
{formFields.map((form, index) => {
return (
<div key={index}>
<input
name='name'
placeholder='Name'
onChange={event => handleFormChange(event, index)}
value={form.name}
/>
<input
name='age'
placeholder='Age'
onChange={event => handleFormChange(event, index)}
value={form.age}
/>
<select
className="custom-select"
value={form.food}
onChange={event => handleSelectChange(event,index)}
>
<option value="steak">Steak</option>
<option value="sandwich">Sandwich</option>
<option value="dumpling">Dumpling</option>
</select>
<button onClick={() => removeFields(index)}>Remove</button>
</div>
)
})}
</form>
<button onClick={addFields}>Add More..</button>
<br />
<button onClick={submit}>Submit</button>
</div>
);
}
export default FoodPreferences;
I have tried using the select component alone without looping it and it worked fine. The errors pop up when select component is placed under a map() for dynamic inputs (Adding or Removing Fields). I know that the error is either in the onChange part of my code for the select component or the handleSelectChange
import React, {useState} from 'react';
function FoodChoice() {
const \[foodState, setFoodState\] = useState("dumpling");
return (
<div className="container p-5">
<select
className="custom-select"
value={foodState}
onChange={(e) => {
const selectedFood = e.target.value;
setFoodState(selectedFood);
}}
>
<option value="steak">Steak</option>
<option value="sandwich">Sandwich</option>
<option value="dumpling">Dumpling</option>
</select>
{foodState}
</div>
);
}
export default FoodChoice;

Related

How to pass key:value onChange from select-option in reactjs?

I follow this tutorial and try to modify it a little.
I have this code addRequestForm.js
import { BiPlus } from 'react-icons/bi'
import { useMutation, useQueryClient } from "react-query"
import { addRequest, getRequests } from "../../lib/helper"
import Bug from "./bugRequest"
import Success from "./successRequest"
export default function AddRequestForm({ formData, setFormData }) {
const queryClient = useQueryClient()
const addMutation = useMutation(addRequest, {
onSuccess: () => {
queryClient.prefetchQuery('requests', getRequests)
}
})
const handleSubmit = (e) => {
e.preventDefault();
if (Object.keys(formData).length == 0) return console.log("Don't have Form Data");
let { sector, contact_name } = formData;
const model = {
sector, contact_name
}
addMutation.mutate(model)
}
if (addMutation.isLoading) return <div>Loading!</div>
if (addMutation.isError) return <Bug message={addMutation.error.message}></Bug>
if (addMutation.isSuccess) return <Success message={"Added Successfully"}></Success>
return (
<form className="grid lg:grid-cols-2 w-4/6 gap-4 md:px-4 md:mx-auto" onSubmit={handleSubmit}>
<div className="input-type">
<label htmlFor="sector" className="block text-sm font-medium text-gray-700">
Sector
</label>
<select
id="sector"
name="sector"
autoComplete="sector-name"
className="border w-full px-5 py-3 focus:outline-none rounded-md"
onChange={(e) => setFormData({ [e.target.name]: e.target.value })}
>
<option value="North">North</option>
<option value="South">South</option>
<option value="West">West</option>
<option value="East">East</option>
{/* */}
</select>
</div>
<div className="form-control input-type">
<label className="input-group">
<span>Contact Name</span>
<input type="text" onChange={setFormData} name="contact_name" placeholder="Contact Name Here..“ className="w-full input input-bordered" />
</label>
</div>
<button type="submit" className="flex justify-center text-md w-2/6 bg-green-500 text-white px-4 py-2 border rounded-md hover:bg-gray-50 hover:border-green-500 hover:text-green-500">
Add <span className="px-1"><BiPlus size={24}></BiPlus></span>
</button>
</form >
)
}
For name=“contact_name” already succeed insert into database (mongodb + mongoose). But, I’m still confuse, how to do the same to select-option?
Already tried these:
onChange={(e) => setFormData({ e.target.name: e.target.value })}
onChange={(e) => setFormData({ sector.name: e.target.value })}
onChange={(e) => setFormData({ [e.target.name][0]: e.target.value })}
Got Syntax error: Unexpected token, expected ",". While I thought this is the right value when I’m consoling these ( result: “sector:myname”).
onChange={(e) => setFormData({ sector: e.target.value })}
onChange={(e) => setFormData({ [e.target.name]: e.target.value })}
No insert into database.
How to write it correctly? I’m using nextjs (i thought the same with reactjs, right?) + #reduxjs/toolkit 1.9.1.
In case needed, here is reducer.js:
import UpdateRequestForm from "./updateRequestForm";
import AddRequestForm from "./addRequestForm";
import { useSelector } from "react-redux";
import { useReducer } from "react";
const formReducer = (state, event) => {
return {
...state,
[event.target?.name]: event.target?.value
}
}
export default function FormRequest() {
const [formData, setFormData] = useReducer(formReducer, {})
const formId = useSelector((state) => state.app.client.formId)
return (
<div className="container mx-auto py-5">
{formId ? UpdateRequestForm({ formId, formData, setFormData }) : AddRequestForm({ formData, setFormData })}
</div>
)
}
try this
onChange = {(e) => {
let obj = {} ;
obj[e.target.name] = e.target.value;
setFormData(obj);
}
}
I'm not entirely following your use case for the reducer but you could replace it with a simple handleChange function as follows. Just a side note, calling setFormData(//newData) will complete erase what is in there so we first spread the original state and change the value based on the key.
State and handleChange:
const [formData, setFormData] = React.useState({
sector: "North",
contact_name: ""
});
const handleChange = ({ target }) => {
// Deconstruct the target
// Deconstruct the name and value from the target
const { name, value } = target;
// Set the state
setFormData((previousState) => ({
...previousState,
[name]: value,
}));
};
Usage:
<select
placeholder="Select a sector"
name="sector"
value={formData.sector}
onChange={handleChange}
>
<option value="North">North</option>
<option value="South">South</option>
<option value="West">West</option>
<option value="East">East</option>
</select>
<input
value={formData.contact_name}
placeholder="Contact Name"
name="contact_name"
onChange={handleChange}
/>
A codesandbox : https://codesandbox.io/s/interesting-kilby-emcyl4?file=/src/App.js:461-615
Also note that you will need to assign a name to the input element. In the example, name is set to sector. As the name value will be used to indicate which key in the state object must be updated.
It's good practice to have controlled input fields. So setting the value of the input to your state will accomplish this. Only thing to remember is you need a default state. I have added it all to the codesandbox if you would like to take a look.
In my case, just use this syntax:
…
value={formData.sector}
onChange={setFormData}
…
Case closed.

How to pass values from dropdown to inputs on select ? ReactJS

I don't know how to pass values from dropdown on select to 4 inputs. My dropdown showing address as it should be but I struggle to pass each value to separate inputs.e.g address_1 should be in one input where is flat number, address_2 where is street etc.
const [select,setSelect]=useState()
<select onChange={(e) => setSelect(e.target.value)}>
<option>Select Address</option>
{getAddressData?.address? (
getAddressData?.address?.map((address: any, id) => (
<option>{`${address.Address_1},${address.Address_2}, ${address.Town}, ${address.County}`}</option>
<input>
type={'text'}
name={'flatNumber'}
placeholder="Flat Number"
value={select}
</input>
Right now I have whole line of address e.g (30 Ballot Street,Smethick,West Midlands) in the whole one input.
Here's the working code. It's live here: https://codesandbox.io/s/competent-firefly-0qdrij?file=/src/App.js
import React, { useState, useEffect } from "react";
function App() {
const [address, setAddress] = useState({
Address_1: "",
Address_2: "",
Town: "",
pafCounty: ""
});
let getAddressData = {};
getAddressData.address = [
{
Address_1: "Address_1",
Address_2: "Address_2",
Town: "Town",
pafCounty: "pafCounty"
},
{
Address_1: "Address_11",
Address_2: "Address_22",
Town: "Town2",
pafCounty: "pafCounty2"
}
];
function handleChange(e) {
setAddress({ ...address, [e.target.name]: e.target.value });
}
useEffect(() => {
console.log(address);
}, [address]);
return (
<>
<select
onChange={(e) => {
setAddress(getAddressData?.address[e.target.value]);
}}
>
<option selected disabled>
Select Address
</option>
{getAddressData?.address?.map((address, index) => (
<option
key={index}
value={index}
>{`${address.Address_1}, ${address.Address_2}, ${address.Town}, ${address.pafCounty}`}</option>
))}
</select>
{Object.keys(address).map((key, index) => (
<input
type="text"
name={key}
placeholder="Flat Number"
value={address[key]}
onChange={handleChange}
/>
))}
</>
);
}
export default App;
Instead of setSelect(e.target.value), set it to the original structured address object. You could have the value of the option be the index within the address list, for example, and then setSelect(getAddressData?.address[2]) or whatever.

Passing values between components in React

I m beginner to reactJS and I m having so much trouble with self learning.
I want to print the data I get from first page.
I used 2 .js files
This is userpage.js:
import resultPage from "./resultPage";
// JS
//const input = document.getElementById('myText');
//const inputValue = input.value
// React
// value, onChange
const Multi = () => {
const [person, setPerson] = useState({ firstName: "", email: "", age: "" });
const [people, setPeople] = useState([]);
const handleChange = (e) => {
const name = e.target.name;
const value = e.target.value;
setPerson({ ...person, [name]: value });
};
const handleSubmit = (e) => {
//e.preventDefault();
if (person.firstName && person.email && person.age) {
const newPerson = { ...person, id: new Date().getTime().toString() };
setPeople([...people, newPerson]);
setPerson({ firstName: "", email: "", age: "" });
resultPage(people, person);
}
};
return (
<>
<article className="form">
<form>
<div className="form-control">
<label htmlFor="firstName">Name : </label>
<input
type="text"
id="firstName"
name="firstName"
value={person.firstName}
onChange={handleChange}
/>
</div>
<div className="form-control">
<label htmlFor="email">Email : </label>
<input
type="email"
id="email"
name="email"
value={person.email}
onChange={handleChange}
/>
</div>
<div className="form-control">
<label htmlFor="age">Age : </label>
<input
type="number"
id="age"
name="age"
value={person.age}
onChange={handleChange}
/>
</div>
<button type="submit" className="btn" onClick={handleSubmit}>
add person
</button>
</form>
</article>
</>
);
};
export default Multi;
This has 2 textboxes and a submit button.
This code is from resultPage.js:
function resultPage(people, person) {
return (
<article>
{people.map((person) => {
const { id, firstName, email, age } = person;
return (
<div key={id} className="item">
<h4>{firstName}</h4>
<p>{email}</p>
<p>{age}</p>
</div>
);
})}
</article>
);
}
export default resultPage;
What am I doing wrong? I m new to reactjs. So kindly spare my obvious mistakes and help me.
From React documentation
HTML form elements work a bit differently from other DOM elements in React, because form elements naturally keep some internal state.
You need to add handleSubmit to your form, and it'll work. As #Halcyon suggested, using a Capital case for a component is good. It's tough to distinguish between HTML elements and components if you use lowercase. Read this for more details.
I am attaching a working sandbox for your code.
You're calling resultPage in handleSubmit. That's not going to work. You want resultPage to be part of the rendering, probably conditionally.
Consider something like:
return <>
{person.firstName !== "" && <resultPage people={people} person={person} />}
{person.firstName === "" && <>
// the form
</>}
</>;
Also since resultPage is a component, it's best to capitalize it.
I think you probably want a 3rd component:
const MyComponent = () => {
const [ people, setPeople ] = React.useState([]);
const [ isEditing, setIsEditing ] = React.useState(false);
return <>
{isEditing && <Multi setPeople={(people) => {
setPeople(people);
setIsEditing(false);
}}
{isEditing === false && <resultPage people={people} />}
</>;
}
Mutli now accepts a prop setPeople that is called in handleSubmit.

how to handle multiple form input with dynamic dropdown render in react Js

I have created a form with multiple input there two dropdown first has option and second one's option changes dynamically based on selection of first dropdown. also i am storing all values in state with submit event.
Okay problem is i am either able to to get values and in state on submit event or either able to dynamically change the option for second dropdown.
But when I am trying to do both or trying to run two function on same onChange event its not trigging is any one can help me out why its not getting trigger and what is the issue.
link : if any one want to see code it dummy code
https://codesandbox.io/s/clever-mendel-ozt8tu?file=/src/App.js:0-1748
App.js
import "./styles.css";
import { useState } from "react";
function App() {
const data = {
first: ["car", "bus"],
second: ["bike", "train"],
third: ["plane", "rocket"]
};
const [option, setOption] = useState([]);
const getInput = (e) => {
let input = e.target.value;
setOption(data[input]);
};
const [values, setValues] = useState({
name: "",
city: "",
category: "",
ride: ""
});
const inputHandle = (e) => {
const { name, value } = e.target;
setValues({
...values,
[name]: value
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log(values);
};
return (
<>
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="name"
name="name"
value={values.name}
onChange={inputHandle}
/>
<input
type="text"
placeholder="city"
name="city"
value={values.city}
onChange={inputHandle}
/>
<br />
<select
name="category"
values={values.category}
onChange={(e) => {
getInput();
inputHandle();
}}
>
<option defaultValue="category">category</option>
<option value="first">first</option>
<option value="second">Second</option>
<option value="third">Third</option>
</select>
<select name="ride" value={values.ride} onChange={inputHandle}>
{option.map((ele) => (
<option value={ele}>{ele}</option>
))}
</select>
<br />
<button type="submit">Submit</button>
</form>
</>
);
}
export default App;
For the handler of the onChange in the first select, you are taking in the event object e as parameter but not passing it forward in getInput() or inputHandle(). Therefore, you should write something like this:
onChange={(e) => {
getInput(e);
inputHandle(e);
}}
This should do the job. Also, try to use only one function in the handler, like the one you have used for submitting the form.
If you want to combine everything into one handler, you can use the following handler:
const categoryHandler = (event) => {
const selectName = event.target.name;
const selectValue = event.target.value;
setOption(data[selectValue]);
setValues({
...values,
selectName: selectValue
});
};

Previous state rendering in Chat thread React JS

I have created a form which has two dropdowns when I select both of them and click on Add Story Button then a chat thread is added.
The problem is when I select New Intent Name and New Action Name from dropdown then the previous Action Name also gets added I don't want like that what I want is
Problem:-
One Intent Name can Have multiple Actions But not duplicate actions
I think I am not setting or mapping the state variable properly, please guide me as of where am I going wrong
While I am able to manage the 1st point I want help with the below two
My Code
import React, { useEffect, useState } from "react";
import {
Form,
Input,
Button,
Select,
Card,
Typography,
notification,
} from "antd";
import { Layout } from "antd";
const { Header, Footer, Content } = Layout;
const { Text } = Typography;
const { Option } = Select;
const CreateStory = () => {
const [form] = Form.useForm();
const [storyValue, setStoryValue] = useState("")
const [intentName, setIntentName] = useState([])
const [actionName, setActionName] = useState([])
const [valueIntent, setValueIntent] = useState("")
const [valueAction, setValueAction] = useState("")
const [results,setResults] = useState([])
const [uniqueResults,setUniqueResults] = useState([])
const storyInputValue = (e) => {
setStoryValue(e.target.value);
};
const onFinish = (values) => {
// console.log("Success:", values);
};
const onFinishFailed = (errorInfo: any) => {
console.log("Failed:", errorInfo);
};
const SelectIntentName = (valueIntent) => {
setValueIntent(valueIntent)
console.log(valueIntent)
};
const SelectActionName = (valueAction) => {
// console.log(valueAction)
setValueAction(valueAction);
setActionName(prev => [...prev,valueAction])
};
// Error Notification
const openNotificationWithIcon = (type) => {
notification[type]({
message: "intent name cannot be empty",
});
};
const addStory = () => {
// setActionName(prev => [...prev,valueAction])
results.push({
intent_name: valueIntent,
// if valueAction is already present then don't push it into the array
actions: [...new Set(actionName)]
})
const removedDup = ([...new Map(results.map((item, key) => [item["intent_name"], item])).values()])
// setUniqueResults(prev => removedDup)
setUniqueResults(removedDup)
}
console.log(uniqueResults)
return (
<div className="csi-create-story-component-page-0103CS">
<Card
title="Create Story"
className="csi-create-story-screen-card-0104SC"
size="small"
>
<Form
onFinish={onFinish}
onFinishFailed={onFinishFailed}
layout="vertical"
>
<Form.Item
label="Story Name"
name="Story Name"
rules={[
{ required: true, message: "Please input your story name!" },
]}
>
<Input
value={storyValue}
onChange={storyInputValue}
placeholder="Enter story name"
/>
</Form.Item>
<div className="csi-action-intent-box-grid-column-0126">
<Form.Item
label="Intent Name"
name="Intent Name"
rules={[
{ required: true, message: "Please select your intent name!" },
]}
>
<Select
placeholder="Select a option"
allowClear
showSearch
onSelect={SelectIntentName}
>
<Option value="intent_name_1">intent_name_1</Option>
<Option value="intent_name_2">intent_name_2</Option>
<Option value="intent_name_3">intent_name_3</Option>
</Select>
</Form.Item>
<Form.Item
label="Action Name"
name="Action Name"
rules={[
{ required: true, message: "Please select your action name!" },
]}
>
<Select
placeholder="Select a option"
allowClear
showSearch
onSelect={SelectActionName}
>
<Option value="action_name_1">action_name_1</Option>
<Option value="action_name_2">action_name_2</Option>
<Option value="action_name_3">action_name_3</Option>
</Select>
</Form.Item>
</div>
<Form.Item>
<Button type="primary" htmlType="submit" onClick={addStory}>
ADD STORY
</Button>
</Form.Item>
</Form>
</Card>
<div>
<Layout className="csi-created-story-list-screen-card-0105SLS">
<Header>{storyValue}</Header>
<Content className="csi-intent-action-content-layout-0353IA">
<div
className="csi-created-intent-action-parent-box-0237IA"
>
{uniqueResults.map((uniqueResult,index) => {
return(
<div key={index}>
<div className="csi-intent-name-left-box">
<span className="csi-intent-text-com-0245I">
<span className="csi-INTENT-text">Intent</span>
<Text>{uniqueResult.intent_name}</Text>
</span>
</div>
<div className="csi-action-name-right-box">
<span className="csi-action-text-com-0246A">
<span className="csi-ACTION-text">Action</span>
<Text>{uniqueResult.actions[index]}</Text>
</span>
</div>
</div>
)
})}
</div>
{/* <div
className="csi-created-intent-action-parent-box-0237IA"
>
{intentName.map((intentName, index) => {
return (
<>
<div className="csi-intent-name-left-box" key={index}>
<span className="csi-intent-text-com-0245I">
<span className="csi-INTENT-text">Intent</span>
<Text>{intentName}</Text>
</span>
</div>
{actionName.map((actionName, index) => {
return (
<div className="csi-action-name-right-box" key={Math.random().toString()}>
<span className="csi-action-text-com-0246A">
<span className="csi-ACTION-text">Action</span>
<Text>{actionName}</Text>
</span>
</div>
);
})}
</>
)
})}
{actionName.map((actionName, index) => {
return (
<div className="csi-action-name-right-box" key={index}>
<span className="csi-action-text-com-0246A">
<span className="csi-ACTION-text">Action</span>
<Text>{actionName}</Text>
</span>
</div>
);
})}
</div> */}
</Content>
<Footer className="csi-footer-submit-button-for-intent-action-0357">
<Button type="primary">Submit</Button>
</Footer>
</Layout>
</div>
</div>
);
};
export default CreateStory;
It is definitely because of index in key prop. React doesn't know what you want to render there.
Don't use index as key if you're going to change order of items
The main issue relies with your implementation. You can not handle it with two array of strings.
My opinion -
Use a result array which will have items as object -
{
"intent_name": "",
"actions": [] //array of string
}
whenever Add Story button is clicked give an entry to this result array. condition should be like - if intent_name already exist then -> check if object.actions already have entry -> if not then give entry.
and loop through this result array to show the intent action list.
you can also use id instead of name in terms of uniqueness.
overall if you need any clarification then please ping me.
and apologies if I miss anything. I am trying this platform new.

Resources