How to resolve "A control must be associated with a text label" on a datalist's option? - reactjs

Building a dropdown and referencing Bootstrap 5 docs' on Datalists it shows an example of:
<label for="exampleDataList" class="form-label">Datalist example</label>
<input class="form-control" list="datalistOptions" id="exampleDataList" placeholder="Type to search...">
<datalist id="datalistOptions">
<option value="San Francisco">
<option value="New York">
<option value="Seattle">
<option value="Los Angeles">
<option value="Chicago">
</datalist>
Writing my component to handle drop downs I'm unsure how to resolve the ESLint error of:
A control must be associated with a text label
code:
import React from 'react'
import PropTypes from 'prop-types'
import { Field } from 'formik'
const Drop = ({ name, label, id, placeholder, options }) => {
return (
<div className="form-group mb-4">
<label htmlFor={name}>{label}:</label>
<Field
aria-label={`${name}-dropdown`}
name={name}
className="form-control"
list={id}
id={`${id}DropDown`}
placeholder={placeholder}
/>
<datalist id={id}>
{options.map((option, key) => (
<option key={key} value={option.toString()} data-value={option.toString()} />
))}
</datalist>
</div>
)
}
Drop.propTypes = {
name: PropTypes.string.isRequired,
label: PropTypes.string.isRequired,
id: PropTypes.string.isRequired,
placeholder: PropTypes.string.isRequired,
options: PropTypes.array.isRequired,
}
export default Drop
Research
React Informed eslint associated control error after adding "Text" as control component
A control must be associated with a text label
Label must have associated control
In my option how can I resolve the ESLint error?

I was able to resolve my issue by adding an aria-label to the option so this:
<datalist id={id}>
{options.map((option, key) => (
<option key={key} value={option.toString()} data-value={option.toString()} />
))}
</datalist>
turned into:
<datalist id={id}>
{options.map((option, key) => (
<option key={key} value={option.toString()} aria-label={option.toString()} data-value={option.toString()} />
))}
</datalist>
after going through the docs and finding control-has-associated-label.

Related

how do i get specific data from the state on a button click in react

Here i have 3 products each have it's own addtocart button and it's option like color, size, quantity. so when i click addtocart button after selecting the options it updating the state and giving me exactly i wanted. The problem is when i selelct any product options and then i click on another product addtocart button it shows the selceted option. not the product options of the addtocart button i clicked
for example: i select the 1st product and choose it's options and i did'nt click on the 1st product addtocart button either i clicked 2nd product button but it returns 1st product selected options it should return 2nd product options.
i need to implement which ever the product button i clicks it should only return that product selected options only.it should'nt return any other product selected options.
how do i make this happen. Help me out.
function Card() {
const [items, setItems] = useState({});
const handleChageCategory = (key, event) => {
setItems((oldState) => ({ ...oldState, [key]: event.target.value }));
};
const submitHandler = () => {
console.log(items);
setItems({});
};
return (
<div className="main-container">
<div className="container">
<div className="image-container">
<img
src="https://images.pexels.com/photos/9558601/pexels-photo-9558601.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
/>
</div>
<h2> T-Shirt </h2>
</div>
<div className="form-conatiner">
<div className="selectors">
<p>Solid Round Neck T-shirt</p>
<select
id="color"
name="color"
required
onChange={(event) => handleChageCategory("color", event)}
>
<option>Color</option>
<option value="black">Black</option>
<option value="green">Green</option>
<option value="orange">Orange</option>
</select>
<select
name="quantity"
required
onChange={(event) => handleChageCategory("quantity", event)}
>
<option>Quantity</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select
name="size"
required
onChange={(event) => handleChageCategory("size", event)}
>
<option>Size</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
<option value="small">Small</option>
</select>
<div>
<button onClick={submitHandler}>Add to Cart</button>
</div>
</div>
</div>
{/* second product */}
<div className="container">
<div className="image-container">
<img
src="https://images.pexels.com/photos/440320/pexels-photo-440320.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
/>
</div>
<h2> i-Watch </h2>
</div>
<div className="form-conatiner">
<div className="selectors">
<p>Dizo watch with amlod </p>
<select
id="2"
name="color"
required
onChange={(event) => handleChageCategory("brand", event)}
>
<option>Brand</option>
<option value="Apple">Apple</option>
<option value="Samsung">Samsung</option>
<option value="Pixel">Pixel</option>
</select>
<select
name="qantity"
required
onChange={(event) => handleChageCategory("qantity", event)}
>
<option>Quantity</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select
name="type"
required
onChange={(event) => handleChageCategory("type", event)}
>
<option>type</option>
<option value="29mm">29mm</option>
<option value="34mm">34mm</option>
<option value="42mm">42mm</option>
</select>
<div>
<button onClick={submitHandler}>Add to Cart</button>
</div>
</div>
</div>
{/* third product */}
<div className="container">
<div className="image-container">
<img
src="https://images.pexels.com/photos/1661471/pexels-photo-1661471.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
/>
</div>
<h2> Hoodie </h2>
</div>
<div className="form-conatiner">
<div className="selectors">
<p>Adidas hoodie with zip </p>
<select
id="2"
name="color"
required
onChange={(event) => handleChageCategory("color", event)}
>
<option>Color</option>
<option value="Gray">gray</option>
<option value="White">white</option>
<option value="Cyan">cyan</option>
</select>
<select
name="qantity"
required
onChange={(event) => handleChageCategory("qantity", event)}
>
<option>Quantity</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select
name="size"
required
onChange={(event) => handleChageCategory("size", event)}
>
<option>type</option>
<option value="39(S)">39(S)</option>
<option value="42(M)">42(M)</option>
<option value="46(L)">46(L)</option>
</select>
<div>
<button onClick={submitHandler}>Add to Cart</button>
</div>
</div>
</div>
</div>
);
}
export default Card;
const [items, setItems] = useState([]);
The handleChangeCategory Logic
const handleChageCategory = (key,product, event) => {
const isExist = items.find((item)=>item.id===product.id)
let updatedItem
if(isExist){
updatedItem = {...isExist, [key]: event.target.value}
setItems((oldState)=>(oldState.map(item=>item.id===updatedItem.id ? updatedItem: item)))
}else{
updatedItem = { ...product, [key]: event.target.value };
setItems((oldState)=>([...oldState, updatedItem]))
}
};
Submit handler logic:
const submitHandler = (product) => {
const isExist = items.find(item=>item.id===product.id)
const newCartItem = isExist? isExist: product
console.log(newCartItem);
setItems((oldState)=>(oldState.filter(item=>item.id!==newCartItem.id)))
}
I know this is an expensive operation and it might hamper user experience but you wanted to do it this way. To handle add to cart, I think the good way is to open a modal or new page after clicking Add To Cart button that also solves your problem because in this way the user is not able to update one product value and click another product button.
A more efficient way to implement a product list is to use a product component for each item which accepts the product data through props. There you will implement all the logic related to the product and check the selected fields and so on.
Full code here.
So here I created Product, which has a selectedValues state that contains the values of all the selectors as an object, and also an handleAddToCart function that sends this product to the parent component function and add the product to the cart there.
Product.js:
const Product = (props) => {
const [selectedValues, setSelectedValues] = useState({});
const { name, description, img, selectors } = props;
//(`selectors` is an array of objects. Each object is a selector with an id field and an array of the `options`).
const handleChangeCategory = (e) => {
setSelectedValues((prevState) => {
return { ...prevState, [e.target.name]: e.target.value };
});
};
const handleAddToCart = () => {
props.addToCart({ name, description, img, selectedValues });//calling the function from the props.
};
return (
<div className="product">
<div className="image-container">
<img width="150px" src={img} alt="" />
</div>
<h2> {name}</h2>
<div className="form-conatiner">
<p>{description}</p>
<div className="selectors">
{selectors.map((s) => {
return (
<select
id={s.id}
name={s.id}
required
onChange={handleChangeCategory}
>
{s.options.map((opt) => (
<option value={opt}>{opt}</option>
))}
</select>
);
})}
</div>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
</div>
);
};
export default Product;
And this is the parent component, where you have the cart and the addToCartHandler function. And in the JSX you simply create a product by calling the Product component and passing the data and the selectors you want to have for this product.
App.js (parent component):
export default function App() {
//Some dummy cart.
const DUMMY_CART = [];
//Function that adds a product to the DUMMY_CART
const addToCartHandler = (product) => {
//Do some logic with the cart of course. I'm just pushing it here to show you the result.
DUMMY_CART.push(product);
console.log('Added product:', product);
console.log('Cart:', DUMMY_CART);
};
return (
<div>
{/* Product 1 */}
<Product
img="https://images.pexels.com/photos/9558601/pexels-photo-9558601.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
name="T-Shirt"
description="Solid round neck T-Shirt"
selectors={[
{ id: 'color', options: ['Color', 'blue', 'grin', 'yellow'] },
{ id: 'quantity', options: ['Quantity', 1, 2, 3] },
{ id: 'size', options: ['Size', 'medium', 'large', 'small'] },
]}
addToCart={(product) => addToCartHandler(product)}
/>
</div>
);
}
Hope this helps. Please check the full code to see how it works.

Show react component on click

So I'm creating a website for the receptionists at a doctor's office. In order to make a new booking, I have a plus button, and when you click it, I want the makeBooking component to pop up and then there's also a cancel button.
Currently, I am struggling to make the component appear onClick.
This is my code:
This is in the appointments component above return
// handleBooking
const [showResults, setShowResults] = React.useState(false)
const handleBooking = () => setShowResults(true)
This is in return:
<button class='addBtn' id="btn"><div className='plus-icon' onClick={handleBooking}><UilPlus/></div></button>
{ showResults ? <MakeBooking /> : null }
And this is my component:
import React from 'react';
import { UilTimes } from '#iconscout/react-unicons';
const MakeBooking = () => {
return (
<div>
<div className='appointments-form'>
<form id="homeForm">
<button class='closeBtn' id="btn"><div className='close-icon'><UilTimes/></div></button>
<div className='heading'>
<h1>Make booking</h1>
<h3>Let's get this clients pet booked!</h3>
</div>
<div className='new-book-container home'>
<input list="docList" className='booking-input home' type='text' placeholder='doctor'/>
<datalist id="docList">
<option value="Sarah"/>
<option value="Josh"/>
<option value="Daina"/>
<option value="Tanielle"/>
<option value="Tony"/>
</datalist>
<input list="clientList" className='booking-input home' type='text' placeholder='client'/>
<datalist id="clientList">
<option value="Sarah"/>
<option value="Josh"/>
<option value="Daina"/>
<option value="Tanielle"/>
<option value="Tony"/>
</datalist>
<input className='booking-input home' type='date' placeholder='Date'/>
<input className='booking-input home' type='time' placeholder='time'/>
<input className='booking-input home' type='number' placeholder='room'/>
<button className='primary-btn' id='btn'>Book appointment</button>
</div>
</form>
</div>
</div>
);
};
export default MakeBooking;
Try this it will work:
const [showResults, setShowResults] = React.useState(false)
const handleBooking = event => {setShowResults(true)};
{ showResults && <MakeBooking /> }

how to pass array values to Formik select

I am using Formik for a bunch of admin console forms that I have in my application. So far I did not have this use case.
My Formik forms use one of 2 custom components, either a Myinputtext(input box) or a MySelect(drop down). I dont have a need for any other components so far. Here is how my Myselect component looks like.
const MySelect = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<select className={props.className} {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
Over in the form I am passing values to this component like this
<MySelect className="select-input" label="Losing Player" name="losingPlayer">
<option value="">Select Losing Player</option>
<option value="player1">{state.Player1Name} </option>
<option value="player2">{state.Player2Name} </option>
All of this works for a few forms I have built so far. In the fourth form now, data coming back from the back end is coming as an array and I am trying to pass the array as input to the myselect component
<MySelect className="select-input" label="Losing Player" name="losingPlayer">
<option value="">Select Losing Player</option>
<option value="player1">{name of array object} </option>
This is failing and not providing the right result.
In the formik official docs it says there is a way to handle array objects like this
<Form>
<Field name="friends[0]" />
<Field name="friends[1]" />
<button type="submit">Submit</button>
</Form>
</Formik>
But my array size can be dynamic and I cannot hardcode, 0,1 like above.
I tried rendering the array inside the select component like this,
<MySelect className="select-input" label="Winning Player" name="winningPlayer">
{props.initialValues.map((player) => {
<option key={player} value={player}> {player} </option> })} </MySelect>
this does not throw any errors. but the drop down is displayed empty.
I am basically hoping to have the names in the array displayed as the dropdown. What is the right solution to tackle this?
This finally worked:-
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
{!props.player ? <select className={props.className} {...field} {...props} />
:
<select className={props.className}>
{props.player.map((player) => {
return (
<option key={player} value={player}>
{player}
</option>
)
})}
</select>
}
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
You need to map your array and render options inside your select like this:
{options?.map(({ value }) => (
<option key={value} value={value}>
{value}
</option>
))}

Dynamic Forms in Netlify

I have two components in React that in a sense are both forms. One component is a parent form of some sorts that contains a map function that iterates over a children state item to produce a number of children sub forms. This means that people using the forms can click a button to add a new instance of a ChildSubForm component beneath the button in the parent form.
My current issue is that I cannot get the form to pick up the children in Netlify forms. I have data-netlify="true" and the hidden input so the parent form is recognised, however when I submit the form, only the inputs in the parent form are picked up, how can I change my settings or code to allow netlify to detect the dynamic components' values so that they are sent alongside the other data?
As you can see, I also attempted to store all form data in state items and submit them, but I cannot see the values still. Is it something to do with Netlify building the static site and detecting all form values beforehand?
BookNow.js (The parent form)
import React, { Component } from 'react'
import ChildSubForm from './ChildSubForm'
export default class BookNow extends Component {
state = {
name: "",
email: "",
otherInfo: "",
children: []
}
constructor(props) {
super(props)
this.addChild = this.addChild.bind(this);
this.decrementChild = this.decrementChild.bind(this);
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
addChild() {
this.setState({
children: [...this.state.children, { name: "", year: "Year 7" }]
})
}
decrementChild(i) {
this.setState({
children: this.state.children.filter((item, j) => i !== j)
})
}
handleChange(e) {
if (["name", "year"].includes(e.target.className)) {
let children = [...this.state.children]
children[e.target.dataset.id][e.target.className] = e.target.value
this.setState({ children }, () => console.log(this.state.children))
} else {
this.setState({ [e.target.name]: e.target.value })
}
}
handleSubmit(e) {
e.preventDefault()
}
render() {
if(this.props.isChild) {
return (
<form name="Booking Form" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="Booking Form" />
<input type="text" name="name" placeholder="Your Name" />
<select name="year-group">
<option value="Year 7">Year 7</option>
<option value="Year 8">Year 8</option>
<option value="Year 9">Year 9</option>
<option value="Year 10">Year 10</option>
<option value="Year 11">Year 11</option>
<option value="Year 12">Year 12</option>
<option value="Year 13">Year 13</option>
<option value="GCSE Retake">GCSE Retake</option>
</select><br />
<input type="email" name="email" placeholder="Your Email Address" /><br />
<textarea name="otherInfo" placeholder="Any more information..." /><br />
<button type="submit">Book Now</button>
</form>
)
} else {
return (
<form onChange={this.handleChange} onSubmit={this.handleSubmit} name="Booking Form" method="POST" data-netlify="true">
<input type="hidden" name="form-name" value="Booking Form" />
<input type="text" name="name" placeholder="Your Name" /><br />
<input type="email" name="email" placeholder="Your Email Address" /><br />
<button onClick={this.addChild} name="add">+ Add Child</button><br />
{
this.state.children.map((child, i) => <ChildSubForm key={i} childNum={i} value={this.state.children[i]} dec={() => this.decrementChild(i)} />)
}
<textarea name="otherInfo" placeholder="Any more information..." /><br />
<button type="submit">Book Now</button>
</form>
)
}
}
}
ChildSubForm.js
import React, { Component } from 'react'
export default class ChildSubForm extends Component {
render() {
const values = {
name: `child-name-${this.props.childNum}`,
year: `child-${this.props.childNum}-year-group`
}
return (
<div className="ChildSubForm">
<input type="text" id={values.name} name={values.name} data-id={this.props.childNum} value={this.props.value.name} className="name" placeholder="Child's Name" required />
<select name={values.year} id={values.year} data-id={this.props.childNum} id={values.year} value={this.props.value.year} className="year">
<option value="Year 7">Year 7</option>
<option value="Year 8">Year 8</option>
<option value="Year 9">Year 9</option>
<option value="Year 10">Year 10</option>
<option value="Year 11">Year 11</option>
<option value="Year 12">Year 12</option>
<option value="Year 13">Year 13</option>
<option value="GCSE Retake">GCSE Retake</option>
</select>
<button onClick={this.props.dec} name="remove">- Remove Child</button><br />
</div>
)
}
}
UPDATE:
So I did some reading and found a post looking to do the same thing and the Director of Support for Netlify saying it isn't possible yet unless you predefine all of the form fields beforehand (for each of the mapped components). Has anybody found a workaround for this?
Netlify Post

option disabled selected not set as default value in React js dropdown

In the dropdown below, why is Select City not appearing in the dropdown as the default selection on page load ? Instead, City1 appears as the default selected option.
I have the options for default coded as :
<option value="" disabled selected>
Select City
</option>
The options are coded in another js file as below:
import React from 'react'
const cityoptions = [
{ value: '1', name: 'City1' },
{ value: '2', name: 'City2' },
{ value: '3', name: 'City3' },
....
The following is the component for dropdown.
import React from 'react'
import PropTypes from 'prop-types'
import Select from '../others/input/select'
import cityOptions from './city-options'
const InputCities = ({ value, change }) => (
<div className="edit_caste_div">
<Select
placeholder="Select option"
value={value}
valueChange={e => change('city', e)}
className="edit_city my2"
>
<option value="" disabled selected>
Select City
</option>
{cityOptions.map((e, key) => {
return <option key={key} value={e.value}>{e.name}</option>
})}
</Select>
</div>
)
InputCities.propTypes = {
value: PropTypes.string.isRequired,
change: PropTypes.func.isRequired,
}
You should add a defaultValue attribute to select and set it to the disabled value
<Select
defaultValue="Select City"
valueChange={e => change('city', e)}
className="edit_city my2"
>
<option value="Select City" disabled>
Select City
</option>
{cityOptions.map((e, key) => {
return <option key={key} value={e.value}>{e.name}</option>
})}
</Select>

Resources