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.
Related
I have a simple todo list that consists of multiple inputs.
I made the editing functionality and now everything works as it should, but only once. When I change the input data for the first time, it saves everything to an array with the correct data.
And when I want to do it a second time, then in order to save this data, three inputs must be changed.
I want that even when changing one input, the data is saved in an array (data that has not been changed is overwritten).
Stackblitz code
App.js
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
function editTask(id, newName, newTranslate, newNote) {
const editedTaskList = tasks.map((task) => {
if (id === task.id) {
return { ...task, name: newName , translate: newTranslate , note: newNote };
}
return task;
});
setTasks(editedTaskList);
}
const taskList = tasks
.map((task) => (
<Todo
id={task.id}
name={task.name}
translate={task.translate}
note={task.note}
completed={task.completed}
key={task.id}
editTask={editTask}
tasks={tasks}
/>
));
return (
<div className="todoapp stack-large">
<ul
className="todo-list stack-large stack-exception"
aria-labelledby="list-heading">
{taskList}
</ul>
</div>
);
}
export default App;
I did a check and added the save button onClick which outputs the data to the console. It gives the data correctly the first time, and if the same item in the todo is changed the second time, it gives an empty space instead of the data that has not been changed.
Todo.js
export default function Todo({name, translate, note, editTask, id, tasks}) {
const [isEditing, setEditing] = useState(false);
const [newName, setNewName] = useState(name);
const [newTranslate, setNewTranslate] = useState(translate);
const [newNote, setNewNote] = useState(note);
function handleChange(e) {
setNewName(e.target.value)
}
function handleChangeTranslate(e) {
setNewTranslate(e.target.value);
}
function handleChangeNote(e) {
setNewNote(e.target.value)
}
function handleSubmit(e) {
e.preventDefault();
if (!newName.trim()|| !newTranslate.trim() || !newNote.trim()) {
return;
}
editTask(id, newName,newTranslate,newNote);
setNewName("");
setNewTranslate("");
setNewNote("");
setEditing(false);
}
const editingTemplate = (
<form className="stack-small" onSubmit={handleSubmit}>
<div className="form-group">
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newName || name}
onChange={handleChange}
placeholder="write word"
/>
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newTranslate || translate}
onChange={handleChangeTranslate}
placeholder="write translate"
/>
<input
id={id}
className="todo-text"
type="text"
autoComplete='off'
defaultValue={newNote || note}
onChange={handleChangeNote}
placeholder="write note"
/>
</div>
<div className="btn-group">
<button
type="button"
className="btn todo-cancel"
onClick={() => setEditing(false)}
>
Cancel
</button>
<button type="submit" className="btn btn__primary todo-edit" onClick={()=>console.log(newName, newTranslate, newNote)}>
Save
</button>
</div>
</form>
);
const viewTemplate = (
<div className="stack-small">
<div className="c-cb">
<label className="todo-label" htmlFor={id}>
{name}
</label>
<label className="todo-label" htmlFor={id}>
{translate}
</label>
<label className="todo-label" htmlFor={id}>
{note}
</label>
</div>
<div className="btn-group">
<button
type="button"
className="btn"
onClick={() => setEditing(true)}
>
Edit <span className="visually-hidden">{name}</span>
</button>
</div>
</div>
);
return <li className="todo">{isEditing ? editingTemplate : viewTemplate}</li>;
}
Since you want to keep those preview state which was not edit and still print out those state with the one you edit, you can just remove all the "reset state '' you put, since all your initial state from useState already had a value and is not an empty string "" like this
function handleSubmit(e) {
e.preventDefault();
if (!newName.trim()|| !newTranslate.trim() || !newNote.trim()) {
return;
}
editTask(id, newName,newTranslate,newNote);
setEditing(false);
}
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;
My store.js file
import commentsReducer from "./stateSlices/commentsSlice";
export default configureStore({
reducer: {
dishes: dishesReducer,
comments: commentsReducer,
leaders: leadersReducer,
promotions: promotionsReducer,
},
});
My commentsSlice.js file
import { COMMENTS } from "../../shared/comments";
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
comments: COMMENTS,
};
export const stateSlice = createSlice({
name: "comments",
initialState,
// Updated the reducer file as below
reducers: {
addComment: {
reducer: (state = initialState.comments, action) => {
state.comments = state.comments.concat(action.payload);
},
prepare: (value) => {
return {
payload: {
...value,
date: new Date().toISOString(),
id: Math.random(),
},
};
},
},
},
});
export default stateSlice.reducer;
My dishdetail.js file
const Dishdetail = (props) => {
if (props.dish != null) {
return (
<div className="container ">
<div className="row ">
<div className="col-12 col-sm-5 col-md-5 m-1">
<RenderComments comments={props.comments} />
</div>
</div>
</div>
);
} else {
return <div></div>;
}
};
export default Dishdetail;
RenderComments component implementation
function RenderComments({ comments, commentsName }) {
// created dishId constant to get the dishId of selected dish and
//need only first index dishId and passed it as prop to
//SubmitComment component as file like this
//changed below line
const dishId = comments.map((x) => x.dishId)[0];
if (comments != null) {
return (
<>
<div>
{comments.map((comment) => {
const options = { year: "numeric", month: "short", day: "2-digit" };
return (
<div key={comment.id}>
<ul className="list-unstyled">
<li>{comment.comment}</li>
<li>
--{comment.author} {new Date(comment.date).toLocaleDateString("en-us", options)}
</li>
</ul>
</div>
);
})}
<SubmitComment dishId={dishId} />
</div>
</>
);
} else {
return <div></div>;
}
}
and My SubmitComment.js component
function SubmitComment() {
const [modal, setModal] = useState(false);
const toggle = () => setModal(!modal);
const {
register,
formState: { errors },
handleSubmit,
reset,
} = useForm();
const onSubmit = (data) => {
console.log(data);
// to get the data from input fields
const { rating, author, comment } = data;
//dispatched the addComment action, here dishId is from props
dispatch(addComment({ dishId, rating, author, comment }));
// Finally subscribed to the store by useSelector method in main
// component which is parent component of renderComments component
//which in turn is parent component of SubmitComments component
reset();
};
return (
<>
<div>
<Button color="primary" onClick={toggle}>
Submit
</Button>
<Modal isOpen={modal} toggle={toggle} fade={false}>
<ModalHeader toggle={toggle}>Submit Comment</ModalHeader>
<ModalBody>
<form onSubmit={handleSubmit(onSubmit)}>
<div className="mb-3">
<label htmlFor="rating" className="form-label">
Rating
</label>
<select
name="rating"
className="form-select "
aria-label="Default select example"
{...register("rating")}
>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
</select>
</div>
<div className="mb-3">
<label htmlFor="name" className="form-label">
Your Name
</label>
<input
type="text"
className="form-control"
id="name"
placeholder="Your Name"
name="name"
{...register("name", { required: true, maxLength: "15" })}
/>
<small className="form-text text-danger">
{errors.name?.type === "required" && "This field is required"}
{errors.name?.type === "maxLength" && "Maximum 15 characters are allowed"}
</small>
</div>
<div className="mb-3">
<label htmlFor="comment" className="form-label">
Comment
</label>
<textarea
type="text"
name="comment"
className="form-control"
id="comment"
aria-describedby="comment"
rows="6"
{...register("comment")}
/>
</div>
<button type="submit" className="btn btn-primary">
Submit
</button>
</form>
</ModalBody>
</Modal>
</div>
</>
);
}
export default SubmitComment;
I am expecting that once I click on the submitcomment form, all the values that I entered in the fields namely rating, author and comments should get added in the renderComment component.
like this
I have tried to add dispatch action after clicking on submit in form and tried to use it in rendercomments component file using useSelector but i am unable to do it
So if anyone can describe the redux flow and basic working flow from here so I could implement this functionality
1 Created reducer in commentSlice.js file
2 In Dishdetail component made following changes:
In renderComments function
created dishId constant to get the dishId of selected dish and
need only first index dishId and passed it as prop to
SubmitComment component which is child component of renderComments
3 Received dishId as prop in SubmitComment and passed it as payload along with other valued received from the user input fields to the action addComment using dispatch
4 Finally subscribed the new state in Main component using useSelectore and passed as prop to Dishdetail component which is child component of main component
Successfully achieved the desired functionality.
I have updated the question with the changes.
I have a basic react hook form with field array. With append the form fields replicates as expected. For each array, I want the user to choose some field to enter without entering the other. I am using radio button and useState to achieve this. However, when i change the selection in an array, the selections in the other arrays changes as well. Please how do i correct this ? Or is there a better way to achieve this functionality. Thanks in advance for your help. The code is found below. I also have codeSandbox: https://codesandbox.io/s/usefieldarray-react-hook-form-2yp3vb?file=/src/App.js:0-3753
export default function App() {
const { handleSubmit, control } = useForm({
defaultValues: {
Detail: [
{
userName: {},
officeAddress: {},
homeAddress: {}
}
]
}
});
const { fields, append, remove } = useFieldArray({
control,
name: "Detail"
});
const [checked, setChecked] = useState();
// onChange function for the address forms
const changeAddressForm = (e) => {
setChecked(e.target.value);
};
const onSubmit = async (data) => {};
return (
<div className="App">
<h1>Selecting a different form in each field array</h1>
<form onSubmit={handleSubmit(onSubmit)}>
<ul>
{fields.map((field, index) => {
return (
<li
key={field.id}
className="w3-border w3-border-green w3-padding"
>
<div>
<div className="w3-padding-large">
<label>Username</label>
<Controller
name={`Detail.${index}.userName`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
<div>
<Radio.Group onChange={changeAddressForm} value={checked}>
<Radio value={1}>Office address</Radio>
<Radio value={2}>Home address</Radio>
</Radio.Group>
</div>
<div className="w3-padding-large">
{checked === 1 && (
<div>
<label>Office address</label>
<Controller
name={`Detail.${index}.officeAddress`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
)}
</div>
<div className="w3-padding-large">
{checked === 2 && (
<div>
<label>Home address</label>
<Controller
name={`Detail.${index}.homeAddress`}
control={control}
render={({ field }) => (
<Input
onChange={(value) => field.onChange(value)}
style={{ width: 200 }}
/>
)}
/>
</div>
)}
</div>
</div>
</li>
);
})}
</ul>
<section>
<button
type="button"
onClick={() =>
append({
userName: {},
homeAddress: {},
officeAddress: {}
})
}
>
Append
</button>
</section>
</form>
</div>
);
}
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])