I am using useState hook for my add product form.
When I refresh my page, data is not displaying for the field category (I am trying to display categories, so the user can select category from the list to create a product for that category). But! Data keeping in redux store:
enter image description here
It only shows when I go to another page using react router() and then go back.
This is my code:
export const AddProduct = () => {
const dispatch = useDispatch();
const userId = useSelector(state => state.auth.userId);
const categories = useSelector(state => state.categories.categories);
const [avatar, setAvatar] = React.useState('');
React.useEffect(() => {
dispatch(categoriesActions.fetchCategories());
}, [dispatch]);
const [orderForm, setOrderForm] = React.useState({
title: {
elementType: 'input',
label: 'Title',
elementConfig: {
type: 'text',
placeholder: 'Title'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
price: {
elementType: 'input',
label: 'Price',
elementConfig: {
type: 'text',
placeholder: 'Price'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
description: {
elementType: 'textarea',
label: 'Description',
elementConfig: {
type: 'text',
placeholder: 'Description'
},
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
category: {
elementType: 'select',
label: 'Select category',
elementConfig:
categories.map(category => (
<option key={category._id} value={category.title}>
{category.title}
</option>))
,
value: '',
validation: {
required: true
},
valid: false,
touched: false
},
});
const [formIsValid, setFormIsValid] = React.useState(false);
const addProductData = event => {
event.preventDefault();
const formData = {};
for (let formElementIdentifier in orderForm) {
formData[formElementIdentifier] = orderForm[formElementIdentifier].value;
}
const product = {
userId: userId,
title: formData.title,
price: formData.price,
description: formData.description,
category: formData.category
}
dispatch(productsActions.addProduct(product));
}
const inputChangedHandler = (event, inputIdentifier) => {
const updatedFormElement = updateObject(orderForm[inputIdentifier], {
value: event.target.value,
valid: checkValidity(
event.target.value,
orderForm[inputIdentifier].validation
),
touched: true
});
const updatedOrderForm = updateObject(orderForm, {
[inputIdentifier]: updatedFormElement
});
let formIsValid = true;
for (let inputIdentifier in updatedOrderForm) {
formIsValid = updatedOrderForm[inputIdentifier].valid && formIsValid;
}
setOrderForm(updatedOrderForm);
setFormIsValid(formIsValid);
};
const formElementsArray = [];
for (let key in orderForm) {
formElementsArray.push({
id: key,
config: orderForm[key]
});
}
let form = (
<form onSubmit={addProductData}>
{formElementsArray.map(formElement => (
<Input
key={formElement.id}
elementType={formElement.config.elementType}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
label={formElement.config.label}
hint={formElement.config.hint}
invalid={!formElement.config.valid}
shouldValidate={formElement.config.validation}
touched={formElement.config.touched}
changed={event => inputChangedHandler(event, formElement.id)}
/>
))}
<Button btnType="Success" disabled={!formIsValid}>ORDER</Button>
</form>
)
return (
<div class="wrapper">
<Header />
<article class="main">
<div class="row">
<div class="item--1-4 image-block">
<div class="product-image-group">
<img class="product-image-big" src={`/${avatar}`} />
<hr class="border-divider" />
<input type="file" onChange={e => setAvatar(e.target.files[0].name)} name="imageUrl" id="imageUrl" />
</div>
</div>
<div class="item--3-4">
<div class="item-title">
<h3>Add product</h3>
<hr class="border-divider" />
</div>
{form}
</div>
</div>
</article>
<LeftMenu />
</div>
)
}
This is line in my code related to that select field:
elementConfig:
categories.map(category => (
<option key={category._id} value={category.title}>
{category.title}
</option>))
That's because the first time that component loads, there is nothing in the categories and when the categories are set, you're not setting the orderForm data again. this is called stale props you need to do this:
useEffect(() => {
setOrderForm((oldValue) => ({
...oldValue,
category: {
...oldValue.category,
elementConfig: categories.map((category) => (
<option key={category._id} value={category.title}>
{category.title}
</option>
)),
},
}));
}, [categories])
This way every time categories data is changed you changing the orderForm state accordingly
Related
I have a form that have several input fields and for some reason my component y re-rendering everytime y change the value of my input field which produces to the input to lose focus.
ContactForm.js:
const ContactForm = () => {
const [values, setValues ] = useState({
name: '',
lastname: '',
email: '',
confirmEmail: '',
message: ''
});
const inputs = [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name'
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name'
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email'
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email'
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message'
}
]
const handleSubmit = (e) => {
e.preventDefault();
}
MY child component, FormInput.js:
import React from 'react'
import './FormInput.css';
/* import { Input } from '../atoms/Input'; */
const FormInput = (props) => {
const { id, onChange, ...inputProps } = props;
return (
<div className='formInput'>
{/* <label htmlFor="">Username</label> */}
{/* <Input {...inputProps} onChange={onChange}/> */}
<input {...inputProps} onChange={onChange} />
</div>
)
}
export default FormInput
const onChange = (e) => {
setValues({...values, [e.target.name]: e.target.value});
}
console.log(values);
return (
<form className='contactForm' onSubmit={handleSubmit}>
{inputs.map((input) => (
<FormInput
key={input.id}
{...input}
value={values[input.name]}
onChange={onChange}
/>
))}
<SubmitBtn/>
</form>
)
}
So is there a solution for this, so that my input field doesn´t lose focus after re-rendering? Or should i prevent re-rendering?
you have 3 options here.
move the input array outside of the component so that it is always the same on every iteration. But if you are fetching this from the server, that is not possible.
you can use a useMemo hook on the input array and make sure to pass an empty array as a dependency array.
remove the Math.random function and maybe use a unique id from the server or for the time being you can use the array index (even though it is not advisable).
I have created a small POC. if you remove the useMemo, the input(s) will lose their focus on every re-render.
Following is the code:
import * as React from 'react';
import './style.css';
export default function App() {
const inputs = React.useMemo(
() => [
{
id: Math.random(),
name: 'name',
type: 'text',
placeholder: 'Name',
},
{
id: Math.random(),
name: 'lastname',
type: 'text',
placeholder: 'Last Name',
},
{
id: Math.random(),
name: 'email',
type: 'email',
placeholder: 'Email',
},
{
id: Math.random(),
name: 'confirmEmail',
type: 'email',
placeholder: 'Confirm Email',
},
{
id: Math.random(),
name: 'message',
type: 'text',
placeholder: 'Message',
},
],
[]
);
const [state, setState] = React.useState({
name: '',
email: '',
message: '',
confirmEmail: '',
lastname: '',
});
const handleChange = (e: any) => {
const value = (e.target as HTMLInputElement).value;
const name = (e.target as HTMLInputElement).name;
setState({
...state,
[name]: value,
});
};
const handleSubmit = () => {
console.log('state', state);
};
return (
<div>
{inputs.map((item) => (
<div key={item.id}>
<label>{item.name}: </label>
<input
name={item.name}
onChange={handleChange}
placeholder={item.placeholder}
/>
</div>
))}
<button onClick={handleSubmit}>Submit</button>
</div>
);
}
It's probably because you are calling Math.random in the body of the ContactForm component. You should never call Math.random() during rendering.
In your case, you can probably move the const inputs to outside the component.
I'm a newbie and I try to set up a search engine that will render the products, base on the value I type on the input.
With my code below, I tried that way but it seems that my logic isn't correct.
Could someone go through my code and check it out and can give me some insight afterward, please.
Thank you in advance.
import data from "./utils/data";
const App = () => {
const [searchValue, setSearchValue] = useState("");
const handleChange = (e) => {
setSearchValue(e.target.value);
};
return (
<div>
<SearchInput handleChange={handleChange} searchValue={searchValue} />
<Products data={data} searchValue={searchValue} />
</div>
);
};
const SearchInput = ({ searchValue, handleChange }) => {
return (
<div>
<input
type="text"
placeholder="Search specific item..."
value={searchValue}
onChange={handleChange}
/>
</div>
);
};
export default SearchInput;
function Products({ data, searchValue }) {
const [productsInfo, setProductsInfo] = useState([]);
useEffect(() => {
filteredProducts(data);
}, []);
const filteredProducts = (products) => {
if (searchValue.toLowerCase().trim() === "") {
setProductsInfo(products);
} else {
const seekedItem = productsInfo.filter(
(product) =>
product.name.toLowerCase().trim().includes(searchValue) ===
searchValue.toLowerCase().trim()
);
setProductsInfo(seekedItem);
}
};
const productsData =
productsInfo.length <= 0 ? (
<div>Loading...</div>
) : (
<div>
{productsInfo.map((product, index) => {
return (
<div
key={index}
style={{ backgroundColor: "grey", maxWidth: "300px" }}
>
<h4>{product.name}</h4>
<p>{product.category}</p>
<p> {product.price} </p>
<hr />
</div>
);
})}
</div>
);
return productsData;
}
export default Products;
const data = [
{
category: "Sporting Goods",
price: "$49.99",
stocked: true,
name: "Football",
},
{
category: "Sporting Goods",
price: "$9.99",
stocked: true,
name: "Baseball",
},
{
category: "Sporting Goods",
price: "$29.99",
stocked: false,
name: "Basketball",
},
{
category: "Electronics",
price: "$99.99",
stocked: true,
name: "iPod Touch",
},
{
category: "Electronics",
price: "$399.99",
stocked: false,
name: "iPhone 5",
},
{ category: "Electronics", price: "$199.99", stocked: true, name: "Nexus 7" },
];
export default data;
If you return empty array in second params in useEffect This function fired only once. Try that:
useEffect(() => {
filteredProducts(data);
}, [searchValue]);
.includes return true or false (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes)
Try that:
const filteredProducts = (products) => {
if (searchValue.toLowerCase().trim() === "") {
setProductsInfo(products);
} else {
const seekedItem = productsInfo.filter(
(product) =>
product.name.toLowerCase().trim().includes(searchValue)
);
setProductsInfo(seekedItem);
}
};
Using antd, I have a simple RadioGroup composed of three RadioButtons. Everything works I expect.
Additionally, I defined a button which is supposed to clear the selected button and revert the RadioGroup to the default. It is not working. The underlying data is OK, but the default RadioButton is not visually checked.
The rightmost RadioButton is the default. Selecting either of the other two and pressing the Clear button does not check the rightmost RadioButton.
I have looked at this issue for days and do not see my error. The code is a bit noisy because a database is invovled.
Codesandbox
import React, { useState, useEffect, Fragment } from 'react';
import { Radio } from 'antd';
import { ConsoleLog } from './consoleLog';
const RadioGroup = Radio.Group;
const App = () => {
const [filters, setFilters] = useState(false);
const initFilters = {
wireSizeId: { displayValue: '', type: 'num', dbKey: '' },
colorId: { displayValue: '', type: 'num', dbKey: '' },
insulationId: { displayValue: '', type: 'num', dbKey: '' },
wireTypeId: { displayValue: '', type: 'num', dbKey: '' },
solidStranded: { displayValue: '', type: 'num', dbKey: null },
copperAlum: { displayValue: '', type: 'num', dbKey: null },
};
useEffect(() => {
setFilters(initFilters);
}, []);
useEffect(() => {
if (filters) {
console.log('e1', filters);
}}, [filters]);
const handleClearFilters = () => {
console.log('clear1', filters);
setFilters(initFilters);
};
const handleCheckbox = (props) => {
console.log('checkbox', filters);
console.log('checkbox', props.target.name, props.target.value, props.target.id);
let updated = {};
updated[props.target.name] =
{ displayValue: props.target.id, type: 'num', dbKey: props.target.value };
console.log(updated);
setFilters({...filters, ...updated});
};
return (
(filters?<Fragment>
<ConsoleLog>Render</ConsoleLog>
<div>
<ConsoleLog>Radio {filters['solidStranded'].dbKey}</ConsoleLog>
<RadioGroup
name='solidStranded'
onChange={handleCheckbox}
defaultValue=''
>
<Radio
value='0'
id='Solid'
checked={filters['solidStranded'].dbKey === '0'}
>
Solid
</Radio>
<Radio
value='1'
id='Stranded'
checked={filters['solidStranded'].dbKey === '1'}
>
Stranded
</Radio>
<Radio
value=''
id='none'
checked={filters['solidStranded'].dbKey === null}
>
None
</Radio>
</RadioGroup>
</div>
<div>
<button onClick={handleClearFilters} >
Clear Filters
</button>
</div>
</Fragment>:null)
);
};
export default App;
Use value in RadioGroup instead checked attribute, value must be not null-able
import React, { useState, Fragment } from "react";
import { Radio } from "antd";
const RadioGroup = Radio.Group;
const App = () => {
const initFilters = {
wireSizeId: { displayValue: "", type: "num", dbKey: "" },
colorId: { displayValue: "", type: "num", dbKey: "" },
insulationId: { displayValue: "", type: "num", dbKey: "" },
wireTypeId: { displayValue: "", type: "num", dbKey: "" },
solidStranded: { displayValue: "", type: "num", dbKey: "-1" }, // change
copperAlum: { displayValue: "", type: "num", dbKey: null }
};
const [filters, setFilters] = useState(initFilters);
const handleClearFilters = () => {
setFilters(initFilters);
};
const handleCheckbox = (props) => {
setFilters({
...filters,
solidStranded: {
// change
displayValue: props.target.id,
type: "num",
dbKey: props.target.value
}
});
};
// the ternary operator isn't necessary now.
return filters ? (
<Fragment>
<div>
<RadioGroup
name="solidStranded"
onChange={handleCheckbox}
value={filters["solidStranded"].dbKey} // change
>
<Radio value="0" id="Solid">
Solid
</Radio>
<Radio value="1" id="Stranded">
Stranded
</Radio>
<Radio value="-1" id="none">
None
</Radio>
</RadioGroup>
</div>
<div>
<button onClick={handleClearFilters}>Clear Filters</button>
</div>
</Fragment>
) : null;
};
export default App;
https://codesandbox.io/s/chekbox-example-8vh07?file=/src/App.js
I have a cards with tables inside but once an edit button is clicked, some of the columns of the tables will be input fields.
But I'm having a problem with my code. I'm getting same value from input change of the field.
Here's my code
EditableTable.js
function EditableTable({
columns,
dataSource,
pagination = false,
hasEdit = false,
renderColumn,
}) {
const footer = () => (
<table>
<thead className="ant-table-thead">
<tr>
<td colSpan="1" className="ant-table-cell">
<span>Total</span>
</td>
</tr>
</thead>
</table>
);
const tableColumns = () => {
if (hasEdit) {
return columns.map(column => {
if (column.hasFields) {
return {
title: column.title,
dataIndex: column.dataIndex,
key: column.key,
render: (text, record, index) =>
renderColumn(text, record, index, column.key),
};
}
return column;
});
}
return columns;
};
return (
<div>
<Table
columns={tableColumns()}
dataSource={dataSource}
pagination={pagination}
footer={footer}
/>
</div>
);
}
Here's where I call my component
const [formFields, setFormFields] = useState({
targets: ['', ''],
achievements: ['', ''],
ratings: ['', ''],
});
const dataSource = [
{
key: 0,
title: 'I. Operational Excellence',
hasEdit: true,
listing: [
{
key: '0',
competency: 'Edward King 0',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
{
key: '1',
competency: 'Edward King 1',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
],
},
{
key: 1,
title: 'II. Financial Contribution',
hasEdit: true,
listing: [
{
key: '0',
competency: 'Edward King 0',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
{
key: '1',
competency: 'Edward King 1',
target: '',
achievement: '',
weight: '',
rating: '',
remarks: '',
score: '',
},
],
},
];
const handleChange = (e, key, index) => {
const fields = formFields;
fields[`${key}s`][index] = e.target.value;
setFormFields({ ...fields });
};
const renderColumn = (text, record, index, key) => {
if (isEdit) {
return (
<Form.Item>
{getFieldDecorator(`${key}-${index}`, {
rules: [
{
required: true,
},
],
})(
<Input
placeholder="Input text"
onChange={e => handleChange(e, key, index)}
/>,
)}
</Form.Item>
);
}
return formFields.targets && formFields.targets[index];
};
{ dataSource.map(item => (
<Card key={item.key} className="datatable" title={item.title}>
<Form onSubmit={handleSave}>
<EditableTable
hasEdit={item.hasEdit}
dataSource={item.listing}
columns={columns}
renderColumn={(text, record, index, key) =>
renderColumn(text, record, index, key)
}
/>
</Form>
</Card>
))}
You have to adapt your dataSource if you want to persist the changes.
Just have a look at the example in Antd Table docs. The depending Codesandbox has a full featured running version.
Code from the CodeSandbox to handle an input change:
handleSave = row => {
const newData = [...this.state.dataSource];
const index = newData.findIndex(item => row.key === item.key);
const item = newData[index];
newData.splice(index, 1, { ...item, ...row });
this.setState({
dataSource: newData,
});
};
I'm trying to create a form in my react app. I created an input.js component which is imported into my contact.js component and then mapped. I'm getting a "Warning: Each child in an array or iterator should have a unique "key" prop. Check the render method of input" inside the console, but I don't understand why because every input component has a unique key already set. When I check them in chrome inspector's react tab, they all have a unique key set.
This is my contact.js component:
import React, { Component } from 'react';
import Input from './Input/input';
import Button from './Button/Button';
import Spinner from '../UI/Spinner/Spinner';
import {checkValidity} from '../../shared/utility';
import axios from '../../axios-contact';
class ContactForm extends Component {
state = {
contactForm: {
name: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'text',
id: 'name',
name: 'name',
required: 'required'
} ,
label: 'Name',
htmlFor: 'name',
invalid: 'Please enter your firstname',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 30
},
valid: false,
touched: false
},
company: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'text',
id: 'company',
name: 'company',
required: 'required'
},
label: 'Company',
htmlFor: 'company',
invalid: 'Please enter your company name',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 30
},
valid: false,
touched: false
},
location: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'text',
id: 'location',
name: 'location',
required: 'required'
},
label: 'Company location (city / country)',
htmlFor: 'location',
invalid: 'Please enter your location',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 30
},
valid: false,
touched: false
},
email: {
elementType: 'email',
elementConfig: {
inputprops: {
type: 'email',
id: 'email',
name: 'email',
required: 'required'
},
label: 'Email',
htmlFor: 'email',
invalid: 'Please enter a propper email address',
value: '',
},
validation: {
required: true,
isEmail: true,
minLength: 7,
maxLength: 40
},
valid: false,
touched: false
},
phone: {
elementType: 'input',
elementConfig: {
inputprops: {
type: 'tel',
id: 'phone',
name: 'phone',
required: false
},
label: 'Phone',
htmlFor: 'phone',
invalid: 'Please enter a propper phone number',
value: '',
},
validation: {
required: false,
minLength: 6,
maxLength: 30
},
valid: true,
touched: false
},
message: {
elementType: 'textarea',
elementConfig: {
inputprops: {
type: 'textarea',
id: 'message',
name: 'message',
required: 'required',
rows: 4
},
label: 'Message',
htmlFor: 'message',
invalid: 'Please enter a message',
value: '',
},
validation: {
required: true,
minLength: 2,
maxLength: 500
},
valid: false,
touched: false
},
compliance: {
elementType: 'checkbox',
containerClass: 'custom-control custom-checkbox',
inputClass: 'custom-control-input',
elementConfig: {
inputprops: {
type: 'checkbox',
id: 'gdpr',
name: 'gdpr',
required: 'required'
},
label: 'I consent to having this website store my submitted information so they can respond to my inquiry.',
htmlFor: 'gdpr',
invalid: 'Please give your consent before proceeding',
value: '',
},
validation: {
required: true,
isCheckbox: true,
isToggled: false
},
valid: false,
touched: false
}
},
formIsValid: false,
loading: false,
sent: false
}
contactHandler = ( event ) => {
event.preventDefault();
this.setState( { loading: true } );
const formData = {}
for (let formElementIdentifier in this.state.contactForm) {
formData[formElementIdentifier] = this.state.contactForm[formElementIdentifier].elementConfig.value;
}
axios.post('/contacts.json', formData)
.then(response => {
this.setState({ loading: false, sent: true });
console.log(formData);
})
.catch(error => {
this.setState({ loading: false, sent: true });
console.log(formData);
});
}
inputChangedHandler = (event, inputIdentifier) => {
const updatedContactForm = {
...this.state.contactForm
};
const updatedFormElement = {
...updatedContactForm[inputIdentifier]
};
updatedFormElement.elementConfig.value = event.target.value;
updatedFormElement.valid = checkValidity(updatedFormElement.elementConfig.value, updatedFormElement.validation);
updatedFormElement.touched = true;
updatedFormElement.validation.isToggled = !updatedFormElement.validation.isToggled;
updatedContactForm[inputIdentifier] = updatedFormElement;
let formIsValid = true;
for ( let inputIdentifier in updatedContactForm) {
formIsValid = updatedContactForm[inputIdentifier].valid && formIsValid;
}
this.setState({contactForm: updatedContactForm, formIsValid: formIsValid});
}
render () {
const formElementsArray = [];
for (let key in this.state.contactForm) {
formElementsArray.push({
id: key,
config: this.state.contactForm[key]
});
}
let form = (
<form onSubmit={this.contactHandler} name="contact">
{formElementsArray.map(formElement =>(
<Input
key={formElement.id}
elementType={formElement.config.elementType}
containerClass={formElement.config.containerClass}
inputClass={formElement.config.inputClass}
elementConfig={formElement.config.elementConfig}
value={formElement.config.value}
invalid={!formElement.config.valid}
shoudValidate={formElement.config.validation}
touched={formElement.config.touched}
checked={formElement.config.validation.isToggled}
changed={(event) => this.inputChangedHandler(event, formElement.id)}
exited={(event) => this.inputChangedHandler(event, formElement.id)} />
))}
<Button disabled={!this.state.formIsValid} />
</form>
);
if (this.state.loading) {
form = <Spinner />
}
if (this.state.sent) {
form = <p id="contact-message" className="contact-message">Thank you for your message.<br /> We will respond as soon as possible.</p>
}
return (
<div className="contact">
<section id="contact-form" className="contact-form">
<h1>Contact</h1>
{form}
</section>
</div>
)
}
};
export default ContactForm;
and this is my input.js component:
import React from 'react';
import { NavLink } from 'react-router-dom';
const input = ( props ) => {
let label = <label htmlFor={props.elementConfig.htmlFor}>{props.elementConfig.label}</label>;
let inputElement = null;
let errorlabel = null;
let inputClass = ['input'];
const errorid = [props.elementConfig.id];
if(props.invalid && props.shoudValidate && props.touched) {
inputClass.push('error');
}
switch (props.elementType) {
case ('input'):
inputElement = <input
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops}
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;
break;
case ('email'):
inputElement = <input
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops}
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;
break;
case ( 'textarea' ):
inputElement = <textarea
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops}
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;
break;
case ( 'checkbox' ):
inputElement = <input
className ={[props.inputClass, inputClass.join(' ')].join(' ')}
{...props.elementConfig.inputprops}
value={!props.checked}
onChange={props.changed} />;
label = <label htmlFor={props.elementConfig.htmlFor} className="custom-control-label">This form collects your name, e-mail, phone number, company name, and location so that we may correspond with you. Read our <NavLink to="/privacy" exact>privacy policy</NavLink> for more information. By submitting the form, you consent to have StackApp collect the listed information.</label>;
break;
default:
inputElement = <input
className ={inputClass.join(' ')}
{...props.elementConfig.inputprops}
value={props.elementConfig.value}
onChange={props.changed}
onBlur={props.exited} />;
}
if(props.invalid && props.touched) {
errorlabel = <label id={errorid.join('-error')} className="error" htmlFor={props.elementConfig.htmlFor}>{props.elementConfig.invalid}</label>
};
let output = null;
if(props.elementType === 'checkbox') {
output = [inputElement, label, errorlabel];
} else {
output = [label, inputElement, errorlabel];
}
return (
<div role="group" className={props.containerClass}>
{output}
</div>
)
};
export default input;
What am I missing here?
Even though formElementsArray.map seems like the most likely candidate, it is not the source of the warning in this case. Like you mentioned in the comments, each of your keys is unique by construction. The error comes from input.js where you assign output = [inputElement, label, errorlabel] then render {output} directly. React sees that this is an array, but doesn't know that it is of fixed size and expects each element in the array to have a unique key prop. If you put a key prop on inputElement, label, and errorLabel the warning should go away.