I have form elements with labels and I want to have unique IDs to link labels to elements with htmlFor attribute. Something like this:
React.createClass({
render() {
const id = ???;
return (
<label htmlFor={id}>My label</label>
<input id={id} type="text"/>
);
}
});
I used to generate IDs based on this._rootNodeID but it’s unavailable since React 0.13. What is the best and/or simplest way to do it now?
The id should be placed inside of componentWillMount (update for 2018) constructor, not render. Putting it in render will re-generate new ids unnecessarily.
If you're using underscore or lodash, there is a uniqueId function, so your resulting code should be something like:
constructor(props) {
super(props);
this.id = _.uniqueId("prefix-");
}
render() {
const id = this.id;
return (
<div>
<input id={id} type="checkbox" />
<label htmlFor={id}>label</label>
</div>
);
}
2019 Hooks update:
import React, { useState } from 'react';
import _uniqueId from 'lodash/uniqueId';
const MyComponent = (props) => {
// id will be set once when the component initially renders, but never again
// (unless you assigned and called the second argument of the tuple)
const [id] = useState(_uniqueId('prefix-'));
return (
<div>
<input id={id} type="checkbox" />
<label htmlFor={id}>label</label>
</div>
);
}
This solutions works fine for me.
utils/newid.js:
let lastId = 0;
export default function(prefix='id') {
lastId++;
return `${prefix}${lastId}`;
}
And I can use it like this:
import newId from '../utils/newid';
React.createClass({
componentWillMount() {
this.id = newId();
},
render() {
return (
<label htmlFor={this.id}>My label</label>
<input id={this.id} type="text"/>
);
}
});
But it won’t work in isomorphic apps.
Added 17.08.2015. Instead of custom newId function you can use uniqueId from lodash.
Updated 28.01.2016. It’s better to generate ID in componentWillMount.
Update React 18
React 18 has introduced a new hook which generates a unique ID:
const id = useId();
Hook API docs: https://reactjs.org/docs/hooks-reference.html#useid
From your example, you could call the hook inside a component:
import React, { useId } from 'react'
function TextField = (props) => {
// generate unique ID
const id = useId();
return (
<>
<label htmlFor={id}>My label</label>
<input id={id} type="text"/>
</>
);
}
Following up as of 2019-04-04, this seems to be able to be accomplished with the React Hooks' useState:
import React, { useState } from 'react'
import uniqueId from 'lodash/utility/uniqueId'
const Field = props => {
const [ id ] = useState(uniqueId('myprefix-'))
return (
<div>
<label htmlFor={id}>{props.label}</label>
<input id={id} type="text"/>
</div>
)
}
export default Field
As I understand it, you ignore the second array item in the array destructuring that would allow you to update id, and now you've got a value that won't be updated again for the life of the component.
The value of id will be myprefix-<n> where <n> is an incremental integer value returned from uniqueId. If that's not unique enough for you, consider making your own like
const uniqueId = (prefix = 'id-') =>
prefix + Math.random().toString(16).slice(-4)
There are also hundreds or thousands of other unique ID things out there, but lodash's uniqueId with a prefix should be enough to get the job done.
Update 2019-07-10
Thanks to #Huong Hk for pointing me to hooks lazy initial state, the sum of which is that you can pass a function to useState that will only be run on the initial mount.
// before
const [ id ] = useState(uniqueId('myprefix-'))
// after
const [ id ] = useState(() => uniqueId('myprefix-'))
You could use a library such as node-uuid for this to make sure you get unique ids.
Install using:
npm install node-uuid --save
Then in your react component add the following:
import {default as UUID} from "node-uuid";
import {default as React} from "react";
export default class MyComponent extends React.Component {
componentWillMount() {
this.id = UUID.v4();
},
render() {
return (
<div>
<label htmlFor={this.id}>My label</label>
<input id={this.id} type="text"/>
</div>
);
}
}
Extending #forivall's comment
If the whole goal is to link up a <label> and <input> elements and they don't depend on props, then instead of using auto generated unique id's, the most optimal and performant approach would be to use useRef.
useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component.
Meaning, you can use useRef to mimic instance variables which is not recomputed on props changes. useRef is not only used to reference a DOM element.
Example using an external random ID generator (e.g. loadash)
import React, { useRef } from 'react'
import uniqueId from 'lodash/utility/uniqueId'
function InputField = (props) => {
const {current: fieldId} = useRef(uniqueId('prefix-'))
return (
<div>
<input id={fieldId} type="checkbox" />
<label htmlFor={fieldId}>label</label>
</div>
);
}
Example using a simple custom random ID generator
import React, { useRef } from 'react'
function InputField = (props) => {
const {current: fieldId} = useRef("prefix-" + (Math.random().toString(36)+'00000000000000000').slice(2, 7))
return (
<div>
<input id={fieldId} type="checkbox" />
<label htmlFor={fieldId}>label</label>
</div>
);
}
Explanation:
The above random ID (Math.random().toString(36)+'00000000000000000').slice(2, 7) comes from this stackoverflow answer and will always guarantee 5 characters, compared to Math.random().toString(16).slice(-4) which may return empty strings.
Also, it's important to use a prefix where the prefix must start with a letter ([A-Za-z]) in order for it to be a valid HTML4 id attribute value.
A version without Lodash using hooks:
function useUniqueId() {
const [id] = useState(() => `component-${Math.random().toString(16).slice(2)}`)
return id
}
Hopefully this is helpful to anyone coming looking for a universal/isomorphic solution, since the checksum issue is what led me here in the first place.
As said above, I've created a simple utility to sequentially create a new id. Since the IDs keep incrementing on the server, and start over from 0 in the client, I decided to reset the increment each the SSR starts.
// utility to generate ids
let current = 0
export default function generateId (prefix) {
return `${prefix || 'id'}-${current++}`
}
export function resetIdCounter () { current = 0 }
And then in the root component's constructor or componentWillMount, call the reset. This essentially resets the JS scope for the server in each server render. In the client it doesn't (and shouldn't) have any effect.
I create a uniqueId generator module (Typescript):
const uniqueId = ((): ((prefix: string) => string) => {
let counter = 0;
return (prefix: string): string => `${prefix}${++counter}`;
})();
export default uniqueId;
And use top module to generate unique ids:
import React, { FC, ReactElement } from 'react'
import uniqueId from '../../modules/uniqueId';
const Component: FC = (): ReactElement => {
const [inputId] = useState(uniqueId('input-'));
return (
<label htmlFor={inputId}>
<span>text</span>
<input id={inputId} type="text" />
</label>
);
};
The useId hook will replace the unstable useOpaqueIdentifier in an upcoming stable version of React (already available in the latest React alphas). It will generate stable ids during server rendering and hydration to avoid mismatches.
For the usual usages of label and input, it's just easier to wrap input into a label like this:
import React from 'react'
const Field = props => (
<label>
<span>{props.label}</span>
<input type="text"/>
</label>
)
It's also makes it possible in checkboxes/radiobuttons to apply padding to root element and still getting feedback of click on input.
I found an easy solution like this:
class ToggleSwitch extends Component {
static id;
constructor(props) {
super(props);
if (typeof ToggleSwitch.id === 'undefined') {
ToggleSwitch.id = 0;
} else {
ToggleSwitch.id += 1;
}
this.id = ToggleSwitch.id;
}
render() {
return (
<input id={`prefix-${this.id}`} />
);
}
}
Don't use IDs at all if you don't need to, instead wrap the input in a label like this:
<label>
My Label
<input type="text"/>
</label>
Then you won't need to worry about unique IDs.
Related
There seems to be a lag of one render cycle when I change a select element and when it's state actually changes. I know that there are several similar questions on this but none of them see to work for me. useEffect is still showing the old state from one render cycle before.
Anyone know how to address this?
Parent component code:
import React, {useCallback, useEffect, useState} from 'react'
import Dropdown from '../components/Dropdown.js'
const ValueCalculation = () => {
const industryData = require('../../../backend/standard_data/industry_benchmarks.json')
var industryList = []
industryData.map((record)=>{
var x = record.Industry;
industryList.push(x)
})
const [industry, setIndustry] = useState(industryList[8]);
const updateIndustry = (updatedValue)=>{
console.log(updatedValue); //<--This is updated with the right value!
setIndustry(updatedValue) //<--This is NOT updating the current value
console.log(industry); //<-- This does NOT show the updated value
}
useEffect(()=>{
console.log(industry); //<--Still showing value from previous render cycle
},[])
return (
<div>
<Dropdown
label="Industry"
value={industry}
onChange={(e)=>updateIndustry(e.target.value)}
list={industryList}
/>
</div>
)
}
export default ValueCalculation
Code for Child Dropdown component..
import React from 'react'
const Dropdown = (props) => {
return (
<div className="form-group mb-3">
<label>{props.label}</label>
<select
className="form-control"
value={props.value}
onChange={props.onChange}
>
{
props.list.map(item=>(
<option key={props.list.indexOf(item)} value={item}>{item}</option>
))
}
</select>
</div>
)
}
export default Dropdown
SetState is async so your console.log is going to run before the state has been set. The code you have works correctly as you can see in the sandbox link provided.
const updateIndustry = (updatedValue) => {
//This is updated with the right value!
console.log(updatedValue);
//This is updating correctly and will show on re render
setIndustry(updatedValue);
//This will not work since setState is async
//Console.log() is firing before the state has been set
console.log(industry);
};
As for the useEffect. You will need to add industry as a dependency so that the console.log is called as soon as the state changes :
useEffect(() => {
console.log(industry);
}, [industry]);
Sandbox : https://codesandbox.io/s/hidden-voice-8jvt2f?file=/src/App.js
So, it's a little bit complicated but your state is changing on every rerender cycle, so ur state it's updated after the updateIndustry it's finished (popped out from js callstack). I tested your code, and it is working perfectly and i refactored it a little bit
import React, { useEffect, useState } from "react";
import Dropdown from "./Dropdown.js";
const App = () => {
var industryList = ["a", "b", "c", "d"];
const [industry, setIndustry] = useState(industryList[0]);
useEffect(() => {
console.log(industry);
}, [industry]);
return (
<div>
<Dropdown
label="Industry"
value={industry}
onChange={(e) => setIndustry(e.target.value)}
list={industryList}
/>
</div>
);
};
export default App;
Also, useEffect hook is reexecuted when its dependency changes value, in your case your dependency array is empty so I added [industry] to it.
I have the following two components.
Brands.js is a parent component that contains the SearchField component and passes a few props to it. I'll explain each prop:
brands is a list of Brand entities each containing an Id and a Name,
searchableItems is a list of Brand entities converted to an array of searchable items
setFilteredItems is a useState returned function that sets the filteredItems variable used by a table component
// I'm cutting away most of the code done in this component,
// but if necessary will expand this section.
...
<SearchField
wrapperClassName="ms-auto me-2 h-100"
inputClassName={"form-control-sm"}
items={brands}
searchableItems={searchableItems}
setFilteredItems={setFilteredItems}
itemIdResolver={(item) => item.id}
placeholder="Search for brand..."
/>
...
SearchField.js is a child component that renders a search input above the table in Brands.js component.
import React, {useEffect, useState} from 'react';
import {
Input
} from 'reactstrap';
export default function SearchField({ inputClassName, wrapperClassName, items, itemIdResolver, searchableItems, setFilteredItems, style, placeholder }) {
if (!inputClassName) inputClassName = '';
if (!wrapperClassName) wrapperClassName = '';
if (!itemIdResolver) itemIdResolver = (item) => item.id;
const [filterText, setFilterText] = useState("");
useEffect(() => {
let filteredSearchableItems = searchableItems?.filter(
searchableItem =>
searchableItem.searchString.indexOf(filterText.toLowerCase()) !== -1
);
setFilteredItems(
items?.filter(
item => filteredSearchableItems?.some(fsi => fsi.id === itemIdResolver(item))
));
}, [filterText, items, searchableItems, setFilteredItems, itemIdResolver]);
return (
<div className={"search-field-wrapper " + wrapperClassName}>
<i className="ri-search-line"></i>
<Input
type="text"
className={inputClassName}
placeholder={placeholder}
value={filterText}
onChange={e => setFilterText(e.target.value)}
style={style}
/>
</div>
)
}
The problem:
The SearchField components itemIdResolver causes the Maximum update depth exceeded error since it constantly re-renders the whole page/component.
I don't understand why fitting the itemIdResolver inside the dependency array of SearchField useEffect causes this.
Any help is appreciated.
P.S.
I tried removing the resolver and that got me rid of the maximum update error, so it's definitely due to that.
I am kind of novice in react redux.
In my react project
I have three components
BigForm , FormA and FormB.
In BigForm, there are two state, DataA and DataB, which would be passed into FormA and FormB.
Question:
Whenever I add input value into Form A or Form B, it will also trigger unnecessary render on the other components. I heard redux may help, but I am not sure how to put this in work in this example.
What have thought before:
For some reason, it is my intention to set the state in parent, instead of having the child to held its own state, because at the end, I need to aggregate the data from all other forms for other purpose.
.Memo is not what I want too coz in my real examples, it is not working due to there is other complexity preventing it to work.
How can I avoid it? Example code would be appreciated. (please also see my edit before trying) thanks ; I heard redux may help, but I am not sure how to put this in work in this example
Below is my code:
import React, {useState, useEffect} from "react";
const FormA = (props) => {
useEffect(()=>{ console.log('Form A was just rendered')})
const { dataA, setDataA } = props;
return (
<div>
<input onChange={(e) => setDataA(e.target.value)}></input>
<p>Input Form A{dataA}</p>
</div>
);
};
const FormB = (props) => {
const { dataB, setDataB } = props;
useEffect(()=>{ console.log('Form B was just rendered')})
return (
<div>
<input onChange={(e) => setDataB(e.target.value)}></input>
<p>Input Form B{dataB}</p>
</div>
);
};
export function BigForm (props) {
const [dataA,setDataA] = useState()
const [dataB,setDataB] = useState()
return (
<div className="App">
<FormA dataA={dataA} setDataA={setDataA}></FormA>
<FormB dataB={dataB} setDataB={setDataB}></FormB>
</div>
);
}
I have spent several hours trying to fix this issue with no luck. I have a reusable component that starts with a state of any empty object, the object is given a series of properties based off of props, and then based on user input on each set of radio buttons will show a child component.
Unfortunately, the function that is supposed to update the state that will then trigger whether the user sees the child component is not working.
I believe the issue is in my editDisplay function. Currently when I click one of the radio buttons the new object that should become the new state is logged to the console correctly, but when I use React Devtools to inspect the page, I see that state is not updating. That being said, I could see a case where I am misunderstanding useEffect, and perhaps useEffect is running each time editDisplay is running. Any help would be appreciated.
import React, {useState, useEffect} from 'react'
import {Form} from 'react-bootstrap'
import RatingInput from './RatingInput'
export default function MembershipForm({inputs, name, record, setRecord}) {
const [display, setDisplay] = useState({})
useEffect(()=>{
inputs.forEach(input => {
var newDisplay = display
newDisplay[name+input]=false
setDisplay(newDisplay)})
},[])
const editDisplay = (input, visible) => {
var newDisplay =display
newDisplay[name+input]=visible
console.log(newDisplay)
setDisplay(newDisplay)
}
const questions=inputs.map(input => {
return (
<div key={name+input}>
<Form.Group>
<Form.Label className='mx-2'>Do you have a {name} {input} Rating?</Form.Label>
<Form.Check inline label='Yes' value={true} name={name+input} type='radio' className = 'mx-2' onClick ={() =>{editDisplay(input, true)}} />
<Form.Check inline label='No' value ={false} name={name+input} type='radio' className = 'mx-2' defaultChecked onClick = {() =>{editDisplay(input, false)}} />
</Form.Group>
{display[name+input] && <RatingInput input={input}/>}
</div >
)
})
return (
<div className='ml-3'>
{questions}
</div>
)
}
const editDisplay = (input, visible) => {
var newDisplay = display
newDisplay[name+input]=visible
console.log(newDisplay)
setDisplay(newDisplay)
}
You should perform react state update in an immutable way.
What you are doing above is you are mutating existing state variable (that assignment on first line doesn't add much: newDisplay refers to same object as display). In such case react might not detect change. Do this instead:
var newDisplay = { ... display };
I wanted to wrap DateInput into MyDateInput with pre-filled parse function argument in order not to repeat parse={dateParser} everywhere.
So I created such element:
import React from 'react';
import { DateInput } from 'admin-on-rest';
import moment from 'moment';
const dateParser = date => {
// v is a `Date` object
if (!(date instanceof Date) || isNaN(date)) return;
return moment(date).format('YYYY-MM-DD');
};
const MyDateInput = (props) => {
return (
<DateInput {...props} parse={dateParser} />
);
};
Which did not work as I expected, so from docs I found that I have to do something like this, but source argument not parsed as it has to be:
import { Field } from 'redux-form';
const MyDateInput = (props) => {
return (
<Field name='date' component={DateInput} {...props} parse={dateParser} />
);
};
When I put 2 such inputs into one form it result in merge of input for both of them.
Is there a way to it properly?
Redux Form name prop sets the name of the field in the state that the Field Component will affect.
So you need to give a different name prop every time you want to use the Field component.
You can provide name as a prop from the form that is calling the Wrapped DateInput comp make sure it is unique ever time.