Dynamically set attribute key (e.g. data-{foo}="bar") in React - javascript

Setting an attribute in React is straightforward if you know the key, e.g.
data-500={this.props.YDistance}
but how can one dynamically set part of the key. e.g.
data-{this.props.YDistance}="valueHere"
var Test = React.createClass({
render: function() {
return (
<div data-{this.props.YDistance}="valueHere">
{this.props.children}
</div>
);
}
});
ReactDOM.render(
<Test YDistance="500">Hello World!</Test>,
document.getElementById('container')
);
There are libraries that require this kind of attribute setting (e.g. skrollr et al)

You could use an object to store the dynamic properties and then use JSX spread attributes.
https://facebook.github.io/react/docs/jsx-spread.html
const propBag = { [`data-${foo}`] = 'bar' };
return (
<div {...propBag}>
{this.props.children}
</div>
);
That is using the computed properties feature of ES6 as well as template literal strings. If you are not using ES6 features you could do an ES5 implementation like so:
var propBag = {};
propBag['data-' + foo] = 'bar';
return (
<div {...propBag}>
{this.props.children}
</div>
);

Related

how to write Foreach with out key in react js [duplicate]

I am making a React app that allows you to make a list and save it, but React has been giving me a warning that my elements don't have a unique key prop (elements List/ListForm). How should I create a unique key prop for user created elements? Below is my React code
var TitleForm = React.createClass({
handleSubmit: function(e) {
e.preventDefault();
var listName = {'name':this.refs.listName.value};
this.props.handleCreate(listName);
this.refs.listName.value = "";
},
render: function() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<input className='form-control list-input' type='text' ref='listName' placeholder="List Name"/>
<br/>
<button className="btn btn-primary" type="submit">Create</button>
</form>
</div>
);
}
});
var ListForm = React.createClass({
getInitialState: function() {
return {items:[{'name':'item1'}],itemCount:1};
},
handleSubmit: function(e) {
e.preventDefault();
var list = {'name': this.props.name, 'data':[]};
var items = this.state.items;
for (var i = 1; i < items.length; i++) {
list.data.push(this.refs[items[i].name]);
}
this.props.update(list);
$('#'+this.props.name).remove();
},
handleClick: function() {
this.setState({
items: this.state.items.concat({'name':'item'+this.state.itemCount+1}),
itemCount: this.state.itemCount+1
});
},
handleDelete: function() {
this.setState({
itemCount: this.state.itemCount-1
});
},
render: function() {
var listItems = this.state.items.map(function(item) {
return (
<div>
<input type="text" className="list-form" placeholder="List Item" ref={item.name}/>
<br/>
</div>
);
});
return (
<div>
<form onSubmit={this.handleSubmit} className="well list-form-container">
{listItems}
<br/>
<div onClick={this.handleClick} className="btn btn-primary list-button">Add</div>
<div onClick={this.handleDelete} className="btn btn-primary list-button">Delete</div>
<button type="submit" className="btn btn-primary list-button">Save</button>
</form>
</div>
)
}
});
var List = React.createClass({
getInitialState: function() {
return {lists:[], savedLists: []};
},
handleCreate: function(listName) {
this.setState({
lists: this.state.lists.concat(listName)
});
},
updateSaved: function(list) {
this.setState({
savedLists: this.state.savedLists.concat(list)
});
},
render: function() {
var lst = this;
var lists = this.state.lists.map(function(list) {
return(
<div>
<div key={list.name} id={list.name}>
<h2 key={"header"+list.name}>{list.name}</h2>
<ListForm update={lst.updateSaved} name={list.name}/>
</div>
</div>
)
});
var savedLists = this.state.savedLists.map(function(list) {
var list_data = list.data;
list_data.map(function(data) {
return (
<li>{data}</li>
)
});
return(
<div>
<h2>{list.name}</h2>
<ul>
{list_data}
</ul>
</div>
)
});
var save_msg;
if(savedLists.length == 0){
save_msg = 'No Saved Lists';
}else{
save_msg = 'Saved Lists';
}
return (
<div>
<TitleForm handleCreate={this.handleCreate} />
{lists}
<h2>{save_msg}</h2>
{savedLists}
</div>
)
}
});
ReactDOM.render(<List/>,document.getElementById('app'));
My HTML:
<div class="container">
<h1>Title</h1>
<div id="app" class="center"></div>
</div>
There are many ways in which you can create unique keys, the simplest method is to use the index when iterating arrays.
Example
var lists = this.state.lists.map(function(list, index) {
return(
<div key={index}>
<div key={list.name} id={list.name}>
<h2 key={"header"+list.name}>{list.name}</h2>
<ListForm update={lst.updateSaved} name={list.name}/>
</div>
</div>
)
});
Wherever you're lopping over data, here this.state.lists.map, you can pass second parameter function(list, index) to the callback as well and that will be its index value and it will be unique for all the items in the array.
And then you can use it like
<div key={index}>
You can do the same here as well
var savedLists = this.state.savedLists.map(function(list, index) {
var list_data = list.data;
list_data.map(function(data, index) {
return (
<li key={index}>{data}</li>
)
});
return(
<div key={index}>
<h2>{list.name}</h2>
<ul>
{list_data}
</ul>
</div>
)
});
Edit
However, As pointed by the user Martin Dawson in the comment below, This is not always ideal.
So whats the solution then?
Many
You can create a function to generate unique keys/ids/numbers/strings and use that
You can make use of existing npm packages like uuid, uniqid, etc
You can also generate random number like new Date().getTime(); and prefix it with something from the item you're iterating to guarantee its uniqueness
Lastly, I recommend using the unique ID you get from the database, If you get it.
Example:
const generateKey = (pre) => {
return `${ pre }_${ new Date().getTime() }`;
}
const savedLists = this.state.savedLists.map( list => {
const list_data = list.data.map( data => <li key={ generateKey(data) }>{ data }</li> );
return(
<div key={ generateKey(list.name) }>
<h2>{ list.name }</h2>
<ul>
{ list_data }
</ul>
</div>
)
});
It is important to remember that React expects STABLE keys, meaning you should assign the keys once and every item on your list should receive the same key every time, that way React can optimize around your data changes when it is reconciling the virtual DOM and decides which components need to re-render.
So, if you are using UUID you need to do it at the data level, not at the UI level.
Also keep in mind you can use any string you want for the key, so you can often combine several fields into one unique ID, something like ${username}_${timestamp} can be a fine unique key for a line in a chat, for example.
Keys helps React identify which items have changed/added/removed and should be given to the elements inside the array to give the elements a stable identity.
With that in mind, there are basically three different strategies as described bellow:
Static Elements (when you don't need to keep html state (focus, cursor position, etc)
Editable and sortable elements
Editable but not sortable elements
As React Documentation explains, we need to give stable identity to the elements and because of that, carefully choose the strategy that best suits your needs:
STATIC ELEMENTS
As we can see also in React Documentation, is not recommended the use of index for keys "if the order of items may change. This can negatively impact performance and may cause issues with component state".
In case of static elements like tables, lists, etc, I recommend using a tool called shortid.
1) Install the package using NPM/YARN:
npm install shortid --save
2) Import in the class file you want to use it:
import shortid from 'shortid';
2) The command to generate a new id is shortid.generate().
3) Example:
renderDropdownItems = (): React.ReactNode => {
const { data, isDisabled } = this.props;
const { selectedValue } = this.state;
const dropdownItems: Array<React.ReactNode> = [];
if (data) {
data.forEach(item => {
dropdownItems.push(
<option value={item.value} key={shortid.generate()}>
{item.text}
</option>
);
});
}
return (
<select
value={selectedValue}
onChange={this.onSelectedItemChanged}
disabled={isDisabled}
>
{dropdownItems}
</select>
);
};
IMPORTANT: As React Virtual DOM relies on the key, with shortid every time the element is re-rendered a new key will be created and the element will loose it's html state like focus or cursor position. Consider this when deciding how the key will be generated as the strategy above can be useful only when you are building elements that won't have their values changed like lists or read only fields.
EDITABLE (sortable) FIELDS
If the element is sortable and you have a unique ID of the item, combine it with some extra string (in case you need to have the same information twice in a page). This is the most recommended scenario.
Example:
renderDropdownItems = (): React.ReactNode => {
const elementKey:string = 'ddownitem_';
const { data, isDisabled } = this.props;
const { selectedValue } = this.state;
const dropdownItems: Array<React.ReactNode> = [];
if (data) {
data.forEach(item => {
dropdownItems.push(
<option value={item.value} key={${elementKey}${item.id}}>
{item.text}
</option>
);
});
}
return (
<select
value={selectedValue}
onChange={this.onSelectedItemChanged}
disabled={isDisabled}
>
{dropdownItems}
</select>
);
};
EDITABLE (non sortable) FIELDS (e.g. INPUT ELEMENTS)
As a last resort, for editable (but non sortable) fields like input, you can use some the index with some starting text as element key cannot be duplicated.
Example:
renderDropdownItems = (): React.ReactNode => {
const elementKey:string = 'ddownitem_';
const { data, isDisabled } = this.props;
const { selectedValue } = this.state;
const dropdownItems: Array<React.ReactNode> = [];
if (data) {
data.forEach((item:any index:number) => {
dropdownItems.push(
<option value={item.value} key={${elementKey}${index}}>
{item.text}
</option>
);
});
}
return (
<select
value={selectedValue}
onChange={this.onSelectedItemChanged}
disabled={isDisabled}
>
{dropdownItems}
</select>
);
};
Hope this helps.
Do not use this return `${ pre }_${ new Date().getTime()}`;. It's better to have the array index instead of that because, even though it's not ideal, that way you will at least get some consistency among the list components, with the new Date function you will get constant inconsistency. That means every new iteration of the function will lead to a new truly unique key.
The unique key doesn't mean that it needs to be globally unique, it means that it needs to be unique in the context of the component, so it doesn't run useless re-renders all the time. You won't feel the problem associated with new Date initially, but you will feel it, for example, if you need to get back to the already rendered list and React starts getting all confused because it doesn't know which component changed and which didn't, resulting in memory leaks, because, you guessed it, according to your Date key, every component changed.
Now to my answer. Let's say you are rendering a list of YouTube videos. Use the video id (arqTu9Ay4Ig) as a unique ID. That way, if that ID doesn't change, the component will stay the same, but if it does, React will recognize that it's a new Video and change it accordingly.
It doesn't have to be that strict, the little more relaxed variant is to use the title, like Erez Hochman already pointed out, or a combination of the attributes of the component (title plus category), so you can tell React to check if they have changed or not.
edited some unimportant stuff
Let React Assign Keys To Children
You may leverage React.Children API:
const { Children } = React;
const DATA = [
'foo',
'bar',
'baz',
];
const MyComponent = () => (
<div>
{Children.toArray(DATA.map(data => <p>{data}</p>))}
</div>
);
ReactDOM.render(<MyComponent />,document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
To add the latest solution for 2021...
I found that the project nanoid provides unique string ids that can be used as key while also being fast and very small.
After installing using npm install nanoid, use as follows:
import { nanoid } from 'nanoid';
// Have the id associated with the data.
const todos = [{id: nanoid(), text: 'first todo'}];
// Then later, it can be rendered using a stable id as the key.
const todoItems = todos.map((todo) =>
<li key={todo.id}>
{todo.text}
</li>
)
Another option is weak-key: https://www.npmjs.com/package/weak-key
import weakKey from "weak-key";
const obj1 = {a : 42};
const obj2 = {b : 123};
const obj3 = {a : 42};
console.log(weakKey(obj1)); // 'weak-key-1'
console.log(weakKey(obj2)); // 'weak-key-2'
console.log(weakKey(obj3)); // 'weak-key-3'
console.log(weakKey(obj1)); // 'weak-key-1'
For a simple array of text-strings; I'm trying one of the two ways:
1. encodeURI which is available on both; NodeJS and browser
const WithEncoder = () => {
const getKey = useCallback((str, idx) => encodeURI(`${str},${idx}`), [])
return (
<div>
{["foo", "bar"].map((str, idx) => (
<div key={getKey(str, idx)}>{str}</div>
))}
</div>
)
}
2. window.btoa which is available only in browser.
const WithB2A = () => {
const getKey = useCallback((str, idx) => window.btoa(`${str}-${idx}`), [])
return (
<div>
{["foo", "bar"].map((str, idx) => (
<div key={getKey(str, idx)}>{str}</div>
))}
</div>
)
}
Depends on the situation, choose a uniqueId creator is ok when you just want render silly items, but if you render items like drag&drop etc and you haven't any uniqueId for each item, I recommend remap that data in your redux, mapper, wherever and add for each item an uniqueId (and not in the render like <Item key={...}) because React couldn't perform any check between renders (and with that all the benefits).
With that remapped that you can use that new Id in your Component.
Here is what I have done, it works for reordering, adding, editing and deleting. Once set the key is not changed, so no unnecessary re-render. One PROBLEM which may be a show stopper for some: it requires adding a property to your object at first render say "_reactKey".
Example for functional component in psuedo TS (ie it won't run in snippets):
interface IRow{
myData: string,
_reactKey?:number
}
export default function List(props: {
rows: Array<IRow>
}) {
const {myRows} = props;
const [nextKey, setNextKey] = useState(100);
const [rows, setRows] = useState<Array<IRow>|undefined>();
useEffect(function () {
if (myRows) {
for (let row of myRows){
if (!row._reactKey){
row._reactKey = nextKey;
setNextKey(nextKey+1);
}
}
setRows(myRows);
} else if (!rows) {
setRows([]);
}
}, [myRows, columns]);
addRow(){
let newRow = { blah, blah, _reactKey : nextKey};
setNextKey(nextKey+1);
rows.push(newRow);
setRows({...rows});
}
function MyRow(props:{row:IRow}){
const {row} = props;
return <tr><td>{row._reactKey}</td><td>row.myData</td></tr>
}
return <table>
<tr><th>Index</th><th>React Key</th><th>My Data</th></tr>
rows.map((row, key)=>{
return <MyRow key={row._reactKey} row={row} />
}
</table>
}
I don't use react too much, but the last time I saw this issue I just created a new state array, and tracked the keys there.
const [keys, setKeys] = useState([0]);
const [items, setItems] = useState([value: "", key: 0,])
Then when I add a new item to list, I get the last key from the keys array, add 1, then use setKeys to update the keys array. Something like this:
const addItemWithKey = () => {
// create a new array from the state variable
let newKeyArr = [...keys];
// create a new array from the state variable that needs to be tracked with keys
let newItemArr = [...items];
// get the last key value and add 1
let key = newKeyArr[newKeyArr.length-1] + 1;
newKeyArr.push(key);
newItemArr.push({value: "", key: key,});
// set the state variable
setKeys(newKeyArr);
setItems(newItemArr);
};
I don't worry about removing values from the keys array because it's only being used for iterating in the component, and we're trying to solve for the case where we remove an item from the list and/or add a new item. By getting the last number from the keys array and adding one, we should always have unique keys.
import React, {useState} from 'react';
import {SafeAreaView,ScrollView,StyleSheet,Text,View,Dimensions} from 'react-native';
const {width}=Dimensions.get('window');
function sayfalar(){
let pages=[]
for (let i = 0; i < 100; i++) {
pages.push(<View key={i} style={styles.pages}><Text>{i}</Text></View>)
}
return pages
}
const App=()=>{
return(
<View style={styles.container}>
<ScrollView horizontal={true} pagingEnabled={true}>
{sayfalar()}
</ScrollView>
</View>
)
}
const styles = StyleSheet.create({
container:{
flexDirection:'row',
flex:1
},
pages:{
width:width
}
})
export default App;
Use the mapped index (i)
things.map((x,i) => {
<div key=i></div>
});
Hope this helps.
You can use react-html-id to generate uniq id easely : https://www.npmjs.com/package/react-html-id
The fastest solution in 2021 is to use uniqid: Go to https://www.npmjs.com/package/uniqid for more info but to sum up:
First in your terminal and your project file: npm install uniqid
Import uniqid in your project
Use it in any key that you need!
uniqid = require('uniqid');
return(
<div>
<div key={ uniqid() } id={list.name}>
<h2 key={ uniqid() }>{list.name}</h2>
<ListForm update={lst.updateSaved} name={list.name}/>
</div>
</div>
)
});
I am using this:
<div key={+new Date() + Math.random()}>

JS Variable AS html attribute [duplicate]

Setting an attribute in React is straightforward if you know the key, e.g.
data-500={this.props.YDistance}
but how can one dynamically set part of the key. e.g.
data-{this.props.YDistance}="valueHere"
var Test = React.createClass({
render: function() {
return (
<div data-{this.props.YDistance}="valueHere">
{this.props.children}
</div>
);
}
});
ReactDOM.render(
<Test YDistance="500">Hello World!</Test>,
document.getElementById('container')
);
There are libraries that require this kind of attribute setting (e.g. skrollr et al)
You could use an object to store the dynamic properties and then use JSX spread attributes.
https://facebook.github.io/react/docs/jsx-spread.html
const propBag = { [`data-${foo}`] = 'bar' };
return (
<div {...propBag}>
{this.props.children}
</div>
);
That is using the computed properties feature of ES6 as well as template literal strings. If you are not using ES6 features you could do an ES5 implementation like so:
var propBag = {};
propBag['data-' + foo] = 'bar';
return (
<div {...propBag}>
{this.props.children}
</div>
);

Working with a array of objects in React. Iterating error saying I cannot use object, use array instead

I'm working on a React portfolio project. I have an external data.js file with an array of objects, which I import into the JS file I'm working in. I want to iterate over the array and place each object into it's own div.
I already tried an If Loop and now working with Map(). This is my code:
const Project = () => {
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{project}</div>
</div>
))
)
return (
<div className='project-kader'>
<h1 className='title'><ProjectItem /></h1>
</div>
)
}
I don't get the problem of iterating trough an array of objects. This is the error:
Error: Objects are not valid as a React child (found: object with keys {-list of keys-}). If you meant to render a collection of children, use an array instead.
I must overlook a simple thing, but I'm a little stuck at the moment :-)
From the fact you're using project.id (and the error message from React), we can assume that project is an object, which means you're trying to use an object as a React child here:
<div>{project}</div>
The error is that you can't do that. Instead, use properties from the object to come up with how you want it displayed. For instance, if project has a description property:
<div>{project.description}</div>
Or if it has (say) a description and a number of days estimated for the project, you might do:
<div>{project.description} - {project.days} {project.days === 1 ? "day" : "days"}</div>
And so on. The fundamental thing is to provide React with something it can put in the DOM (loosely, "display"), such as strings, numbers, arrays, true (but not false)...
You need to return array from your ProjectItem i.e you need to do this:
const Project = () => {
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{project}</div>
</div>
))
)
return (
<div className='project-kader'>
<h1 className='title'>{ProjectItem()}</h1>
</div>
)
}
try JSON.stringy(project) like this or you should use your object property.Let's we say project has a property that called name
Here is a working example for you.
const Project = () => {
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{JSON.stringy(project)} || {project.name}</div>
</div>
))
)
return (
<div className='project-kader'>
<h1 className='title'><ProjectItem /></h1>
</div>
)
I hope it can work.
you can use project as object into div element
const ProjectItem = () => (
ProjectenList.map(project => (
<div key={project.id}>
<div>{project.text}</div>
</div>
))
)

How do I join two elements and then render them in React?

It's simple. I have a render function with two elements
render() {
let elem1 = <div>Foo</div>;
let elem2 = <div>Bar</div>;
return(elem1 + elem2); // How do I render elem1 below elem2?
}
I want the output to be equivalent to this
<div>
Foo
</div>
<div>
Bar
</div>
Simple like this:
render() {
let elem1 = <div>Foo</div>;
let elem2 = <div>Bar</div>;
return (
<div>
{elem1}
{elem2}
</div>
);
}
You should read and practice more JSX in here
If you are using React > 16.2 you can use the new Fragment component
render() {
let elem1 = <div>Foo</div>;
let elem2 = <div>Bar</div>;
return (
<React.Fragment>
{elem1}
{elem2}
<React.Fragment/>
);
}
The main advantage over using a div is that it doesn't add any elements to the page and is more readable than returning an array.
render() {
let elements=new Array();
elements.push(<div>Foo</div>);
elements.push(<div>Bar</div>);
return elements;
}
So basically you can return array of children.
You can just return the two elements in the render method instead of declaring them and return them directly. Like this
render() {
return (
<div>
<div>Foo</div>
<div>Bar</div>
</div>
)
}
Extending on #Martin's answer above: If you are starting with latest version of React you should try and use React.Fragment as grouping element. Here is the reactjs documentation for it: https://reactjs.org/docs/fragments.html
There is also a shorter syntax for React.Fragment which is <>{elements}</>. However support for short syntax is not fully available yet.
Here is a codepen link for problem you are trying to solve: https://codepen.io/bdevapatla/pen/QxaKEq

Changing a html variable in react

I have the following variable in React
let Example = (
<div>
Hello World
</div>
);
I would like to change its content (text, tags, etc.)
Is that possiable to do?
What is the type of this variable?
Thanks
You can just overwrite your variable in another statement and this is a jsx Element.
https://reactjs.org/docs/introducing-jsx.html
Maybe you are looking for react components and props?
https://reactjs.org/docs/components-and-props
The type of your variable would be a JavaScript function and you can use react props to modify your content.
Use functions!
const myContent = () => {
// Some js
return 'Hello!';
}
const Example = () => (
<div>
{myContent()}
</div>
);
If you want to change the content of a JSX snippet that's a crystal clear hint that what you need is a component.
In your example, you can try something like this.
const SayHello = ({ name }) => <div>Hello {name}</div>
Then you can use it as a regular component.
<SayHello name="world" />
You can bound the value to a variable, and then the component will be updated with any update of that variable.
let name = this.aMethodToGetAValue();
return <SayHello name={name} />

Categories