I'm building a website with react. I create a component which has grouped Textfield , I don't know how to set the value of those Textfield to the state.
The state format should be like : state:{products:[{},{},{}]}
I've tried to create a button to insert new group of Textfield, and an handleChange method to capture the Textvalue,
but still stuck in how to set states.
export default class extends Component {
state = {
count: 0,
products: []
};
handleAddClick = () => {
this.setState(({count}) => ({
count: count + 1
}))
};
handleChange = e => {
//this is where i stuck
};
render() {
const {count} = this.state;
let items = [];
for (let i = 0; i <= count; i++) {
items.push(
<div key={i}>
<TextField
label="product"
margin="normal"
onChange={this.handleChange}
/>
<TextField
label="color"
margin="normal"
onChange={this.handleChange}
/>
<TextField
label="quantity"
margin="normal"
onChange={this.handleChange}
/>
<TextField
label="price"
margin="normal"
onChange={this.handleChange}
/>
</div>
)
}
return <Fragment>
<Button onClick={this.handleAddClick}>
<AddIcon/>
</Button>
{items}
</Fragment>
}
}
I realize that I have to use some id to identify the different group of TextFields, but where to put it , and how to get it in handleChange method?
Your state seems wrong, you need add one more key which handles input changes, lets call it as product. So, this product will handle the current textboxes and once user click add button you can add that to your products array. this produce will an object.
state = {
count: 0,
product:{},
products: [],
};
pass the textfield value with keys so you can fill the product object key,
<TextField value={this.state.product.product}
label="product"
type="text"
margin="normal"
onChange={(e) => { this.handleChange(e, 'product') }}
/>
And set it like this
handleChange = (e, type) => {
this.setState({
product: {
...this.state.product,
[type]: e.target.value,
},
});
};
And when user clicks on add you can push this produce to products array
handleAddClick = () => {
this.setState({
products: this.state.products.concat(this.state.product),
product: {},
})
};
Here is how your component will look:
Replace input with TextField and button with Button
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
class App extends Component {
state = {
count: 0,
product: {},
products: [],
};
handleAddClick = () => {
this.setState({
products: this.state.products.concat(this.state.product),
product: {
product: "",
color: "",
quantity: "",
price: "",
}
})
};
handleChange = (e, type) => {
//this is where i stuck
this.setState({
product: {
...this.state.product, [
type]: e.target.value,
}
});
};
render() {
console.log(this.state);
const { count } = this.state;
let items = [];
for (let i = 0; i <= count; i++) {
items.push(
<div key={i}>
<input value={this.state.product.product}
label="product" type="text"
margin="normal"
onChange={(e) => { this.handleChange(e, 'product') }}
/>
<input value={this.state.product.color}
label="color"
margin="normal"
onChange={(e) => { this.handleChange(e, 'color') }}
/>
<input value={this.state.product.quantity}
label="quantity"
margin="normal"
onChange={(e) => { this.handleChange(e, 'quantity') }}
/>
<input value={this.state.product.price}
label="price"
margin="normal"
onChange={(e) => { this.handleChange(e, 'price') }}
/>
</div>
)
}
return (
<div>
<button onClick={this.handleAddClick}>
add
</button>
{items}
</div>
)
}
}
render(<App />, document.getElementById('root'));
EDIT:
As you want to iterate over products and change the values,You do not need product any more we will use the product as base values for the products, now following things will be changed, you need to get index of each products and by default we will assign one value
like this
let product = {
product: "",
color: "",
quantity: "",
price: "",
};
class App extends Component {
state = {
products: [Object.assign({},product)],
};
handleAddClick = () => {
var newProduce = {
product: "",
color: "",
quantity: "",
price: "",
}
this.setState({
products: this.state.products.concat(newProduce),
})
};
handleChange = (e, type, index) => {
const copiedData = Object.assign({}, this.state);
copiedData.products[index][type] = e.target.value;
this.setState(copiedData);
};
...
Here is the demo
Related
I want add object inside of array items
I am trying to manage objects inside of an array with useState but is not working i have only in object but I want the object in interior of the array of items. When I click add items on the button i want add the element and if possible remove this element when i click remove items in button link with inputs (see the image)
Like :
company:"Apple",
companyAdress:"5 avenue triagle",
items: [
{
itemName: Computer,
itemQuantity: 20,
itemPrice: 209
},
{
itemName: Computer,
itemQuantity: 20,
itemPrice: 209
},
]
My code :
const [info, setInfo] = useState({});
const [itemForm, setItemForm] = useState({ num: 1 })
const handleRemoveItem = (e) => {
e.preventDefault();
setItemForm((itemForm) => ({ num: itemForm.num - 1 }))
}
const handleAddItem = (e) => {
e.preventDefault();
setItemForm((itemForm) => ({ num: itemForm.num + 1 }))
}
<label>Company</label>
<input onChange={(e) => { setInfo({ ...info, company: e.currentTarget.value}); }} placeholder="Company"></input>
<label>company Adress</label>
<input onChange={(e) => { setInfo({ ...info, companyAdress: e.currentTarget.value }); }} placeholder="Adresse"></input>
<ul className="space-y-3">
{[...Array(itemForm.num)].map((x, i) => {
return (
<li key={i}>
<div>
<input onChange={(e) => { setInfo({...info,itemName: e.currentTarget.value });}} name="itemName" placeholder="itemName:" ></input>
<input onChange={(e) => { setInfo({ ...info, itemQuantity: e.currentTarget.value }); }} type="number" name="itemQuantity" placeholder="Quantity:"></input>
<input onChange={(e) => { setInfo({ ...info, itemPrice: e.currentTarget.value }); }} type="number" name="itemPrice" placeholder="Price:"></input>
<button onClick={handleRemoveItem}>Enlever </button>
<button onClick={handleAddItem}>+ Add New Item</button>
</div>
</li>
)
}
)}
</ul>
i do something like this using an id to find the iteminfo.
i am currying the itemid here but you could put the itemId as part of the input id and find it that way if you like - then you could use one function. anyway hope it helps
also i am just using the id as the key for the object you might what to be more strict on this ¯_(ツ)_/¯
i would also put the factory and defaultInfo else where in your app
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
const defaultItemFactory = () => {
return { itemName: "", itemQuantity: "", itemPrice: "", id: uuidv4() };
};
const defaultInfo = {
company: "",
companyAdress: "",
items: [defaultItemFactory()],
};
function App() {
const [info, setInfo] = useState(defaultInfo);
const changeHanlder = (event) => {
const { id, value } = event.currentTarget;
setInfo((_info) => {
return { ..._info, [id]: value };
});
};
const itemHanlder = (itemId) => (event) => {
const { id, value } = event.currentTarget;
setInfo((_info) => {
if (id === "add")
return { ..._info, items: _info.items.concat(defaultItemFactory()) };
const items = _info.items
.map((item) => {
if (item.id !== itemId) return item;
if (id === "remove") return null;
return { ...item, [id]: value };
})
.filter((out) => out);
return { ..._info, items };
});
};
return (
<div className="App">
<label>Company</label>
<input
id={"company"}
value={info.company}
onChange={changeHanlder}
placeholder="Company"
></input>
<label>company Adress</label>
<input
id={"companyAdress"}
value={info.companyAdress}
onChange={changeHanlder}
placeholder="Adresse"
></input>
<ul className="space-y-3">
{info.items &&
info.items.map((item, i) => {
return (
<li key={`item-${item.id}`}>
<div>
<input
id={"itemName"}
value={item.itemName}
onChange={itemHanlder(item.id)}
name="itemName"
placeholder="itemName:"
></input>
<input
id={"itemQuantity"}
value={item.itemQuantity}
onChange={itemHanlder(item.id)}
type="number"
name="itemQuantity"
placeholder="Quantity:"
></input>
<input
id={"itemPrice"}
value={item.itemPrice}
onChange={itemHanlder(item.id)}
type="number"
name="itemPrice"
placeholder="Price:"
></input>
<button id={"remove"} onClick={itemHanlder(item.id)}>
Enlever{" "}
</button>
<button id={"add"} onClick={itemHanlder(item.id)}>
+ Add New Item
</button>
</div>
</li>
);
})}
</ul>
</div>
);
}
export default App;
I have a function react component which has an object consisting of different values that renders in react form.
const initialInputValues = {
domain: '',
title1: '',
title2: '',
title3: '',
title4: '',
title5: '',
title6: '',
title7: '',
description1: '',
description2: '',
};
const [values, setValues] = useState(initialInputValues);
//handling input
const handleInputChange = (e) => {
const { name, value } = e.target;
setValues({...values, [name]: value });
}
//handle submit
const handleSubmit = (e) => {
e.preventDefault();
}
I have a reusable FormGroup component like this
<FormGroup
label='Domain'
name='domain'
type='url'
value={values.domain}
onChange={handleInputChange}
/>
<FormGroup
label='Title 1'
name='title1'
value={values.title1}
onChange={handleInputChange}
/>
<FormGroup
label='Title 2'
name='title2'
value={values.title2}
onChange={handleInputChange}
/>
<FormGroup
label='Title 2'
name='title2'
value={values.title2}
onChange={handleInputChange}
/>
<FormGroup
label='Title 3'
name='title3'
value={values.title3}
onChange={handleInputChange}
/>
I have a button under this FormGroup Title fields, now I want to append title4, title5, title6, title7 one by one on click of this Add button.
<button
className='button__addField'
type='button'
onClick={handleAddTitle}
>
Add Title
</button>
Is there any other way except using useRef and hide/show using CSS classes? I have tried that but looks like that is not a feasible solution if in future I want to expand title fields up to 12 or description fields up to 8 etc.
Codesandbox: https://codesandbox.io/s/admiring-estrela-r84fv3?file=/src/App.js:2517-2534
Save the number of title fields in a state and create an array of components with the length of that state.
You don't need to change handleInputChange since it'll just merge the new name properties to the state.
export default function App() {
const initialInputValues = {
domain: "",
title1: "",
title2: "",
title3: "",
title4: "",
title5: "",
title6: "",
title7: "",
description1: "",
description2: ""
};
const [values, setValues] = useState(initialInputValues);
const [titleNum, setTitleNum] = useState(3)
const handleInputChange = (e) => {
const { name, value } = e.target;
setValues({ ...values, [name]: value });
};
const handleSubmit = (e) => {
e.preventDefault();
};
return (
<div className="sidebar-area">
<form className="form-area" onSubmit={handleSubmit}>
<div className="form__buttons">
<button className="button__share" value="Share" type="submit">
Share
</button>
<button type="button" className="button__export">
Export
</button>
</div>
<div className="form__heading">
<div className="heading__iconTitle">
<h4 className="heading__title">Campaign Basics</h4>
</div>
</div>
<FormGroup
label="Domain"
name="domain"
type="url"
value={values.domain}
onChange={handleInputChange}
/>
{Array.from({length: titleNum}, (_,i, ind = i + 1) => (
<FormGroup
label= {`Title ${ind}`}
name={`title${ind}`}
value={values[`title${ind}`] || ''}
onChange={handleInputChange}
/>
))}
<div className="form__addField">
<div>
<button
className="button__addField"
type="button"
onClick={() => setTitleNum(prev => prev + 1)}
>
Add Title
</button>
</div>
</div>
<div className="form__group__inlineField">
<FormGroup
label="Path 1"
name="path1"
value={values.path1}
onChange={handleInputChange}
/>
<FormGroup
label="Path 2"
name="path2"
value={values.path2}
onChange={handleInputChange}
/>
</div>
<FormGroup
rows="2"
label="Description Line 1"
name="description1"
value={values.description1}
onChange={handleInputChange}
/>
<FormGroup
rows="2"
label="Description Line 2"
name="description2"
value={values.description2}
onChange={handleInputChange}
/>
</form>
</div>
);
}
If you are effectively wanting a dynamic list of titles then I'd suggest storing an array of titles in the state and appending to this array when you want to add another title input. You will want to use functional state updates to correctly update from the previous values state value and not whatever values value is closed over in callback scope.
Example:
...
import { nanoid } from "nanoid"; // used for GUIDs
const MIN_TITLES = 3;
const MAX_TITLES = 7;
export default function App() {
const initialInputValues = {
domain: "",
// Prepopulate titles array with minimum to display initially
titles: Array.from({ length: MIN_TITLES}).map(() => ({
id: nanoid(),
value: ""
})),
description1: "",
description2: ""
};
const [values, setValues] = useState(initialInputValues);
const handleInputChange = (e) => {
const { name, value } = e.target;
setValues((values) => ({ ...values, [name]: value }));
};
// Update title by id
const handleTitleChange = (e) => {
const { id, value } = e.target;
setValues((values) => ({
...values,
titles: values.titles.map((title) =>
title.id === id
? {
...title,
value
}
: title
)
}));
};
const addTitle = () => {
setValues((values) => {
// Shallow copy titles array and append new entry can still add
if (values.titles.length < MAX_TITLES) {
return {
...values,
titles: values.titles.concat({
id: nanoid(),
value: ""
})
};
}
// Otherwise return previous state, no change
return values;
});
};
const handleSubmit = (e) => {
e.preventDefault();
};
return (
<div className="sidebar-area">
<form className="form-area" onSubmit={handleSubmit}>
...
{values.titles.map((title, i) => (
<FormGroup
key={title.id}
id={title.id}
label={`Title ${i + 1}`}
name={`Title${i + 1}`}
value={title.value}
onChange={handleTitleChange}
/>
))}
<div className="form__addField">
<div>
<button
className="button__addField"
disabled={values.titles.length === MAX_TITLES}
type="button"
onClick={addTitle}
>
Add Title
</button>
</div>
</div>
...
</form>
</div>
);
}
Using arrays for these dynamic fields makes it trivial to add new fields, delete existing entries, and sort them later if necessary. In other words, storing the titles in an array makes them much easier to work with.
Similarly, if the plan is to eventually also make the "Path" and "Description" fields dynamic you would convert them also into arrays.
I don't know how to target the specific object with the id an change the name and the email value together in one setState.(The members will take new objects with different id)
state = {
groupName:"",
members: [
{name:"", email:"", id:uuid.v4()}
]
}
render(){
return(
{this.state.members.map((member) => {
return (
<div key={member.id}>
<TextField
id={member.id}
placeholder="Enter your name..."
onChange={(event) => this.handleInput(event)} />
<TextField
id={member.id}
placeholder="Enter your email"
onChange={(event) => this.handleInput(event )}
/>
</div>
)
})}
)}
handleInput = (event) => {
const newGroup = {...this.state}
const newElement = {...newGroup.members[event.target.id]}
newElement[name] = event.target.value;
newGroup.members[name] = newElement;
this.setState({
state : newGroup
})
}
You need to pass the field value in order to set the correct element within the members object. Also while updating you can map over the members array and update the respective object and in setState you would pass the members as key.
state = {
groupName:"",
members: [
{name:"", email:"", id:uuid.v4()}
]
}
render(){
return(
{this.state.members.map((member) => {
return (
<div key={member.id}>
<TextField
id={member.id}
placeholder="Enter your name..."
onChange={(event) => this.handleInput(event, "name")} />
<TextField
id={member.id}
placeholder="Enter your email"
onChange={(event) => this.handleInput(event, "email" )}
/>
</div>
)
})}
)}
handleInput = (event, name) => {
event.persist();
this.setState(prevState => ({
members : prevState.members.map((item) => {
if (item.id = event.target.id) {
return {
...item,
[name]: event.target.value
}
} else {
return item;
}
})
}))
}
This is because state should be immutable. You can do following:
this.setState(({members})=> {
const updateMembers = members.map(member => {
if (member.id === event.target.id) {
return {
... member,
[name]: event.target.value
}
}
return member;
})
return {members: updateMembers}
})
You can read more about immutability here
I have a select dropdown that when selected renders a checkbox group using <FieldArray> from formik
<FieldArray
name="fields"
render={arrayHelpers => (
<div>
{fields.map(field => (
<div key={field.name}>
<label>
<input
name="fields"
type="checkbox"
value={field.name}
onChange={e => {
if (e.target.checked) arrayHelpers.push(field.name);
else {
const idx = fields.indexOf(field.name);
arrayHelpers.remove(idx);
}
}}
/>{" "}
{field.name}
</label>
</div>
))}
</div>
)}
/>
So in the onChange method I need for each checkbox that is selected to render a class component that has additional input fields that are tied to the field name. For example, the size and length options need to chosen for each checkbox that is selected.
class FieldInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
lengthType: "",
size: [],
};
this.lengthTypeChange = this.lengthTypeChange.bind(this);
this.onSizeChange = this.onSizeChange.bind(this);
}
lengthTypeChange = lengthType => {
//handle change method for lengthType
this.setState({ lengthType });
console.log("LengthType selected: ", lengthType);
};
onSizeChange = e => {
this.setState({ [e.target.name]: e.target.value });
console.log([e.target.value]);
};
render() {
return (
<div>
<h2>
{" "}
These are the input fields for each field name checkbox selected.{" "}
</h2>
<div>
<Select
id="color"
options={lengthTypeOptions}
isMulti={false}
value={lengthType}
onChange={this.lengthTypeChange}
onBlur={this.handleBlur}
placeholder={"Select a lengthType..."}
/>
</div>
<div>
<label>Size:</label>
<input
value={this.state.size}
onChange={this.onSizeChange}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</div>
</div>
);
}
}
For instance each field.name is rendered to a checkbox and should have a select dropdown for lengthType and input for size like so:
{
field.name: {
size: 1,
lengthType: "Fixed"
}
}
So I have the component designed, just need to render it to each checkbox that is selected.
How can I pass the FieldInput component to the checkboxes based on if they are toggled or not?
Inside the <Myselect> component, you can find the <FieldArray> component, using the onChange method
onChange={e => {
if (e.target.checked) {
arrayHelpers.push(field.name);
When's it's checked it also needs to render <FieldInputs>
Here's a link to a sandbox with the classes/components described above
I made many changes this is the full code
import "./helper.css";
import { MoreResources, DisplayFormikState } from "./helper";
import React from "react";
import { render } from "react-dom";
import { Formik, FieldArray } from "formik";
import * as Yup from "yup";
import axios from "axios";
import Select from "react-select";
var MockAdapter = require("axios-mock-adapter");
var mock = new MockAdapter(axios);
mock.onGet("/dataschemas").reply(200, {
data: [
{
id: "2147483602",
selfUri: "/dataschemas/2147483602",
name: "Phone Data"
}
]
});
mock.onGet("/dataschemas/2147483602").reply(200, {
data: {
id: "2147483602",
selfUri: "/dataschemas/2147483602",
type: "DataSchema",
name: "Phone Record",
fields: [
{
name: "action"
},
{
name: "callee"
},
{
name: "caller"
},
{
name: "duration"
},
{
name: "message"
},
{
name: "time_stamp"
}
]
}
});
const lengthTypeOptions = [
{ value: "fixed", label: "Fixed" },
{ value: "variable", label: "Variable" }
];
class FieldInputs extends React.Component {
constructor(props) {
super(props);
this.state = {
lengthType: "",
size: []
};
this.lengthTypeChange = this.lengthTypeChange.bind(this);
this.onSizeChange = this.onSizeChange.bind(this);
}
lengthTypeChange = lengthType => {
//handle change method for lengthType
this.setState({ lengthType }, () => {
this.props.update(this.props.name, this.state);
});
//console.log("LengthType selected: ", lengthType);
};
onSizeChange = e => {
this.setState({ [e.target.name]: e.target.value }, () => {
this.props.update(this.props.name, this.state);
});
//console.log([e.target.value]);
};
render() {
const { lengthType } = this.state;
return (
<div>
<h2>
{" "}
These are the input fields for each field name checkbox selected.{" "}
</h2>
<div>
<Select
id="color"
options={lengthTypeOptions}
isMulti={false}
value={lengthType}
onChange={this.lengthTypeChange}
onBlur={this.handleBlur}
placeholder={"Select a lengthType..."}
/>
</div>
<div>
<label>Size:</label>
<input
value={this.state.size}
onChange={this.onSizeChange}
type="number"
name="size"
min="1"
placeholder="1"
required
/>
</div>
</div>
);
}
}
const App = () => (
<div className="app">
<h1>Formik Demo</h1>
<Formik
initialValues={{
querySchemaName: "",
schemas: [],
fields: [],
selectorField: "",
lengthType: "",
size: []
}}
onSubmit={(values, { setSubmitting }) => {
setTimeout(() => {
alert(JSON.stringify(values, null, 2));
setSubmitting(false);
}, 500);
}}
validationSchema={Yup.object().shape({
querySchemaName: Yup.string()
.required("QuerySchema name is required!")
.min(3, "Please enter a longer name")
.max(50, "Please ener a shorter name")
})}
>
{props => {
const {
values,
touched,
errors,
dirty,
isSubmitting,
handleChange,
handleBlur,
handleSubmit,
handleReset,
setFieldValue,
setTouchedValue
} = props;
return (
<form onSubmit={handleSubmit}>
<label htmlFor="querySchemaName" style={{ display: "block" }}>
QuerySchema Name:
</label>
<input
id="querySchemaName"
placeholder="Example -- QuerySchema1"
type="text"
value={values.querySchemaName}
onChange={handleChange}
onBlur={handleBlur}
className={
errors.querySchemaName && touched.querySchemaName
? "text-input error"
: "text-input"
}
/>
{errors.querySchemaName && touched.emaquerySchemaNameil && (
<div className="input-feedback">{errors.querySchemaName}</div>
)}
<MySelect
value={values.schemas}
onChange={setFieldValue}
options={values.schemas}
onBlur={setTouchedValue}
error={errors.topics}
touched={touched.topics}
/>
<button
type="button"
className="outline"
onClick={handleReset}
disabled={!dirty || isSubmitting}
>
Reset
</button>
<button type="submit" disabled={isSubmitting}>
Submit
</button>
<DisplayFormikState {...props} />
</form>
);
}}
</Formik>
<MoreResources />
</div>
);
class MySelect extends React.Component {
constructor(props) {
super(props);
this.state = {
schemas: [],
fields: [],
selectorField: "",
checked: []
};
this.handleChange = this.handleChange.bind(this);
this.updateSelectorField = this.updateSelectorField.bind(this);
}
componentDidMount() {
axios.get("/dataschemas").then(response => {
this.setState({
schemas: response.data.data
});
//console.log(this.state.schemas);
});
}
handleChange = value => {
// this is going to call setFieldValue and manually update values.dataSchemas
this.props.onChange("schemas", value);
const schema = this.state.schemas.find(
schema => schema.name === value.name
);
if (schema) {
axios.get("/dataschemas/2147483602").then(response => {
this.setState({
fields: response.data.data.fields
});
//console.log("fields are: " + this.state.fields);
});
}
};
updateSelectorField = value => {
this.props.onChange("selectorField", value);
};
update = (name, value) => {
this.setState(prev => {
var arr = prev.checked;
var de = null;
for (var j = 0; j < arr.length; j++) {
if (arr[j].name === name) {
de = j;
}
}
arr[de] = Object.assign(arr[de], value);
console.log(arr);
return { checked: arr };
});
};
handleBlur = () => {
// this is going to call setFieldTouched and manually update touched.dataSchemas
this.props.onBlur("schemas", true);
};
checker = field => {
var d = -1;
for (let j = 0; j < this.state.checked.length; j++) {
if (this.state.checked[j].name === field.name) {
d = j;
}
}
if (d >= 0) {
return (
<FieldInputs
key={field.name + 1}
name={field.name}
update={this.update}
/>
);
} else {
return null;
}
};
change = e =>{
var arr = this.state.checked;
var de = -1;
for (var j = 0; j < arr.length; j++) {
if (arr[j].name === e) {
de = j;
}
}
if(de >= 0){
delete arr[de];
}else{
var arr = arr.concat([
{
name: e,
size: 1,
lengthType: "Fixed"
}
])
}
var nar = [];
for(let i=0; i<arr.length; i++){
if(typeof arr[i] !== "undefined"){
nar.push(arr[i])
}
}
this.setState({checked: nar});
}
render() {
const schemas = this.state.schemas;
const fields = this.state.fields;
return (
<div style={{ margin: "1rem 0" }}>
<label htmlFor="color">
DataSchemas -- triggers the handle change api call - (select 1){" "}
</label>
<Select
id="color"
options={schemas}
isMulti={false}
value={schemas.find(({ name }) => name === this.state.name)}
getOptionLabel={({ name }) => name}
onChange={this.handleChange}
onBlur={this.handleBlur}
placeholder={"Pick a DataSchema..."}
/>
<label htmlFor="color">Selector Field - (select 1) </label>
<Select
id="color"
options={fields}
isMulti={false}
value={fields.find(({ name }) => name === this.state.name)}
getOptionLabel={({ name }) => name}
onChange={this.updateSelectorField}
placeholder={"Select a Selector Field..."}
/>
{!!this.props.error && this.props.touched && (
<div style={{ color: "red", marginTop: ".5rem" }}>
{this.props.error}
</div>
)}
<div>
<FieldArray
name="fields"
render={arrayHelpers => (
<div>
{fields.map(field => (
<React.Fragment>
<div key={field.name}>
<label>
<input
name="fields"
type="checkbox"
value={field.name}
onChange={(e) => {
this.change(field.name)
if (e.target.checked) {
arrayHelpers.push(field.name);
} else {
const idx = fields.indexOf(field.name);
arrayHelpers.remove(idx);
}
}}
/>{" "}
{field.name}
</label>
</div>
{this.checker(field)}
</React.Fragment>
))}
</div>
)}
/>
</div>
</div>
);
}
}
render(<App />, document.getElementById("root"));
here is the codesandbox
I'm having an issue where React seems to be rendering a deleted array element after I've removed it from an array in the components state. After setState(), rendering is triggered, but the deleted item is shown, instead of the remaining item (see GIF video below).
Although the component is extremely simple and I've spent hours on this problem, I haven't been able to solve this issue. The strange thing is that the newState object actually contains the valid new list, but it's not rendered.
I really hope someone can help me figure this out!
import React from "react";
import Button from "#material-ui/core/Button";
import update from "immutability-helper";
import Grid from "#material-ui/core/Grid";
import TextField from "#material-ui/core/TextField";
import * as R from "ramda";
class SessionNoteGroup extends React.Component {
state = {
id: this.props.id,
notes: this.props.notes
};
render() {
const { classes } = this.props;
const { id, notes } = this.state;
return (
<div>
<Grid container>
<Grid item xs={12}>
<TextField
multiline
fullWidth
id="notes"
name="notes"
label="Notes"
rows="2"
value={notes}
onChange={this.handleValueChange}
/>
</Grid>
</Grid>
<Button variant="outlined" color="primary" onClick={this.handleDelete}>
Delete
</Button>
</div>
);
}
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
let newState = {
id: id,
notes: value
};
this.setState(newState);
};
handleDelete = () => {
this.props.onDelete(this.state.id);
};
}
class SessionNotes extends React.Component {
state = {
notes: this.props.notes.slice(),
deleted: []
};
next_id = 2;
createNotes = () => {
let notesList = [];
for (let i = 0; i < this.state.notes.length; i++) {
const { id, notes } = this.state.notes[i];
notesList.push(
<SessionNoteGroup
id={id}
notes={notes}
onDelete={this.handleDelete}
index={i + 1}
/>
);
}
console.log(notesList);
return notesList;
};
handleDelete = id => {
const newNotes = R.filter(note => note.id !== id, this.state.notes);
this.setState({ notes: newNotes });
};
handleClickAdd = async () => {
const note = {
id: this.next_id,
notes: ""
};
this.next_id++;
const newState = update(this.state, { notes: { $push: [note] } });
this.setState(newState);
};
render() {
return (
<div>
{this.createNotes()}
<Button
variant="outlined"
color="primary"
onClick={this.handleClickAdd}
>
Add
</Button>
</div>
);
}
}
export default SessionNotes;
Few things.
When you want to set the state, based on the prev state, use the setState with a callback, which takes as argument the prevState. So the next code:
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
let newState = {
id: id,
notes: value
};
this.setState(newState);
};
Will be something like:
handleValueChange = event => {
const { name, value } = event.target;
const { id, notes } = this.state;
this.setState(prevState => ({ id: prevState.id, notes: value}));
};
Same in the below component:
handleDelete = id => {
const newNotes = ;
this.setState(prevState => ({ notes: R.filter(note => note.id !== id, prevState.notes) }));
};
And so on for all the times that you update the state based on previous state value.
Then when you do create a list of elements in react, use key property:
<SessionNoteGroup
key={id}
id={id}
notes={notes}
onDelete={this.handleDelete}
index={i + 1}
/>
That's used by react for managing the render of list of items
Try adding a key to the container div of your render
return (
<div key = {this.props.id}>
<Grid container>
<Grid item xs={12}>
<TextField
multiline
fullWidth
id="notes"
name="notes"
label="Notes"
rows="2"
value={notes}
onChange={this.handleValueChange}
/>
</Grid>
</Grid>
<Button variant="outlined" color="primary" onClick={this.handleDelete}>
Delete
</Button>
</div>
);