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()}>
My state.events is in array that is made up of the component instance: EventContainer.
I want my setState to place a new EventContainer in the state.events array. However, I want that EventContainer to go in the index immediately after the specific EventContainer that made the setState call.
I'm looking for help with making the adjustments necessary to my approach or, if my entire approach is bad, a recommendation on how to go about this. Thank you very much.
I'm developing an itinerary builder which is made up of rows/EventContainers that represent an activity on a given day.
Each EventContainer has a button that needs to offer the user the ability to onClick an additional row immediately after that EventContainer.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState (index){
let newEvent = <EventContainer />;
this.setState(prevState => {
const events = prevState.events.map((item, j) => {
if (j === index) {
events: [...prevState.events.splice(index, 0, newEvent)]
}
})
})
}
render(){
return (
<>
<div>
<ul>
{
this.state.events === null
? <EventContainer pushNewEventContainerToState= .
{this.pushNewEventContainerToState} />
: <NewEventButton pushNewEventContainerToState={this.pushNewEventContainerToState} />
}
{this.state.events.map((item, index) => (
<li
key={item}
onClick={() =>
this.pushNewEventContainerToState(index)}
>{item}</li>
))}
</ul>
</div>
</>
)
}
}
My goal in setState was to splice newEvent into this.state.events immediately after the index (the parameter in pushNewEventContainerToState function).
I'm getting this error but I'm guessing there's more going on than just this: Line 23:22: Expected an assignment or function call and instead saw an expression no-unused-expressions.
I can see at least 2 issues with the code.
- Splice will mutate the array in place
- You are not returning the updated state.
You can instead use slice to build the new array.
pushNewEventContainerToState(index) {
let newEvent = < EventContainer / > ;
this.setState(prevState => {
const updatedEvents = [...prevState.events.slice(0, index], newEvent, ...prevState.events.slice(index + 1];
return {
events: updatedEvents
})
})
}
As I'm fairly new to coding, it took me awhile but I was able to compile the full answer. Here is the code, below. Below that, I explain, point by point, what the problem was and how the updated code addresses that.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [{key:0}],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState(index) {
let newEvent = {key: this.state.events.length};
this.setState(prevState => {
let updatedEvents = [...prevState.events.slice(0, index + 1), newEvent, ...prevState.events.slice(index + 1)];
return {
events: updatedEvents
};
})
}
render(){
return (
<>
<div>
<ul>
{this.state.events.map((item, index) => (
<li key={item.key}>
< EventContainer pushNewEventContainerToState={() => this.pushNewEventContainerToState(index) } / >
</li>
))}
</ul>
</div>
</>
)
}
}
Setup
Starting with state.events, instead of starting with an empty array, I'm starting with one object, including a key starting at 0, because I always want the user to start with one EventContainer.
Regarding pushNewEventContainerToState, #Sushanth made a great recommendation. Please refer directly to that function in my latest code. The refinement I made has to do with the way I separate the EventContainer being passed to this.state.events. I've moved the EventContainer from pushNewEventContainerToState down to the render() element. I've given it a prop of key={item.key} and wrapped the component instance in a li. The very first EventContainer will have a key of 0 (see state.events[0]). Now, each new EventContainer passed to state.events will have a key that's based off the latest .length() of the state.events array (refer to the latest value of the let newEvent variable in pushNewEventContainerToState).
All of that allowed me to fix a big problem I was facing: I needed the newest EventContainer to be placed in the index immediately after the index of the EventContainer calling pushNewEventContainerToState. The main reason this was happening was because I wasn't properly passing the index to the EventContainer inside of render(). Now that I have the actual EventContainer there, I can pass it a prop in the right manner (please refer EventContainer's prop in render). Now I'm calling pushNewEventContainerToState with the correct index.
I'm learning React by implementing a front-end interface for the note app API that I created. I have succeeded in having a list of all the note titles in my database appear. I want to be able to click on a title and have the note expand into the text of the note. The easiest way I've found for this is to give the "key" attribute of the 'li' as a variable and to also declare the same variable in the JSX { } object because they have the same name.
I've been looking for an answer for this for a few days and have been unable to find this exact problem. You can put a variable in a normal JSX expression but I need to do it on the 'li' which means technically in the HTML.
Here's some code to understand what I'm saying.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
//AXIOS CALL
setTitles(response.data[0]);
});
}, []);
//^^^^^add the array there to stop the response.data from repeating WAY TOO MANY TIMES
let listTitles = titles.map(titles => (
<li className="noteTitles" key={titles.title}>
{titles.title}
</li>
));
let showText = titles.map(titles => (
<li className="openText" key= {titles.text_entry}>
{titles.text_entry}
</li>
))
let openNote = () => {
setOpen(open => !open);
if (open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{showText}
</ul>
</div>
);
}
if (!open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listTitles}
</ul>
</div>
);
}
};
return { openNote };
};
export default NoteData;
That is the code I currently have. Here's showing a more simplified version of the openNote function that maybe makes more sense and shows what I'm trying to do:
VariableHere = "";
let openNote = () => {
setOpen(open => !open);
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
};
let listNotes = titles.map(titles => (
<li className="noteTitles" key={VariableHere}>
{VariableHere}
</li>
));
return (
<div>
<ul onClick={openNote}>
{listNotes}
</ul>
</div>
);
On click of each element there should be a switch of the key elements so if the element is 'open' the key variable and given variable in the JSX object should be mapped to titles.text_entry and on '(!open)' the key and JSX should be mapped to titles.title.
first of all, you're using a ternary in a weird way:
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
Ternaries are meant to be expressions whose value is conditional, but you're using it like a shorthand if/else. Try something like
VariableHere = open ? titles.text_entry : titles.title;
which is both shorter and more readable.
Second of all, keys in an array of elements are meant to help React determine which elements to update, if an item represents the same object, its key shouldn't change. In this case, regardless of what you're displaying, an item in the array represents the same note. Always using the title as the key should be fine provided items can't have the same title. If they can, use some sort of unique ID instead. If the order of the items doesn't change throughout the life of the component, using the array index as the key is fine.
Lastly, what you seem to want to do is called "conditional rendering". There are many ways to achieve this in react, one such way is to use the pre-cited ternary operator. Here is a minimal working example:
const listNotes = titles.map(note => (
<li className="noteTitles" key={note.title}>
{open ? note.title : note.text_entry}
</li>
));
const openNote = () => {
setOpen(!open);
}
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listNotes}
</ul>
</div>
)
You could also use a ternary in the key expression, but as I talked about above, it's not a good idea to do so.
Given your data-structure, I think you can simplify your code a bit. There is no need to create separate arrays for titles and contents. It sounds like you just want to expand and collapse a note when it is selected.
Here is a really simplified version on how you an do this. I'll use a sample data-set since we don't have access to your API.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [currentNote, setCurrentNote] = useState({});
useEffect(() => {
//AXIOS CALL
// setTitles(response.data[0]);
let data = [
{ id: 1, title: "a", text_entry: "what" },
{ id: 2, title: "b", text_entry: "is" },
{ id: 3, title: "c", text_entry: "up?" }
];
setTitles(data);
}, []);
const handleClick = noteId => {
let selectedTitle = titles.find(title => title.id == noteId);
//"collapse" if already selected
if (noteId === currentNote.id) {
setCurrentNote({});
} else {
setCurrentNote(selectedTitle);
}
};
let listTitles = titles.map(title => (
<li
className="noteTitles"
key={title.title}
onClick={() => handleClick(title.id)}
>
{title.title}
{title.id === currentNote.id && <div>{title.text_entry}</div>}
</li>
));
return (
<div>
Click on link item
<ul>{listTitles}</ul>
</div>
);
};
See working sandbox: https://codesandbox.io/s/old-silence-366ne
The main updates:
You don't need to have an "open" state. To be more succinct and
accurate, you should have a currentNote state instead, which is
set when clicking on a list item.
Have your handleClick function accept a noteId as an argument.
Then use that noteId to find the corresponding note in your titles
state. Set that found note as the currentNote. If the selected
note was already the currentNote, simply set currentNote to an
empty object {}, thus creating our expanding/collapsing effect.
In the JSX, after the title, use a ternary operator to conditionally
display the currentNote. If the note being mapped matches the
currentNote, then you would display a div containing the
text_entry.
I have a react component that I need to render a bunch of elements based on the items in an array. I've setup a function to render over all of them in the array and it works nicely. Now I want to split the rendered elements into two different wrapper elements. I tried a few ways of doing this using if statements and switches but my code was getting really messy. I wanted to see if there is a cleaner way to do this, that someone might know.
renderSplit(key) {
return <div className={`inner_${key}`} key={key}>{`inner_${key}`}</div>;
}
render() {
const { arr } = this.props;
return (
<div>
{arr.map(this.renderSplit)}
</div>
);
}
So if I have an array of 5 items I want the first 4 that get rendered by renderSplit to be wrapped in a container
<div className='left'>.....</div> and the last item in the array to be wrapped in another wrapper <div className='right'>....</div>
If there is only one item, in the array it doesn't get wrapped by any wrapper div.
It should be arr.map not body.map next comes pass in the array . Then invoke the function
do this:
renderSplit(arr) {
return arr.map((entry,index)=>
<div className={`inner_${entry}`} key={entry}>{`inner_${entry}`}</div>;
)
}
render() {
const { arr } = this.props;
return (
<div>
{this.renderSplit(arr)}
</div>
);
}
you can input a condition(with respect to the index) inside the map statement , to satisfy the last statement
you can use destructuring assignment to extract the first elements and the last one from the array, check if the last one exists, if it doesn't, that means the array has only one entry, and render accordingly :
renderSplit(key) {
return <div className={`inner_${key}`} key={key}>{`inner_${key}`}</div>;
}
render() {
const { arr } = this.props;
const [last, ...firstElems ] = arr.reverse(); // reverse the array to get the last element and use the spread operator to get the rest.
return (
firstElems.length > 0 // if you have elements in the firstElems array
? (
<div className="wrapper">
<div className="left">{firstElems.reverse().map(this.renderSplit)}</div> // reverse the firstElems array to display it in the right order.
<div className="right">{this.renderSplit(last)}</div> // render the last element.
</div>
)
// other wise, render the single element you have.
: <div className="singleElement">{this.renderSplit(last)}</div>
);
}
Let's see:
renderSplit(key) {
return <div className={`inner_${key}`} key={key}>{`inner_${key}`}</div>;
}
render() {
const { arr } = this.props;
if(!arr || !arr.length) { return null; }
if(arr.length === 1) { return renderSplit(arr[0]); }
const left = arr.slice(0, arr.length - 2);
const right = arr[arr.length -1];
// Or else:
// const rightElement = arr.pop(); const leftElements = arr;
return (
// or <React.Fragment>
<>
<div className="left">
{leftElements.map(this.renderSplit)}
</div>
<div className="right">
{this.renderSplit(rightElement)}
</div>
</>
);
}
I'm trying to convert a jQuery component to React.js and one of the things I'm having difficulty with is rendering n number of elements based on a for loop.
I understand this is not possible, or recommended and that where an array exists in the model it makes complete sense to use map. That's fine, but what about when you do not have an array? Instead you have numeric value which equates to a given number of elements to render, then what should you do?
Here's my example, I want to prefix a element with an arbitrary number of span tags based on it's hierarchical level. So at level 3, I want 3 span tags before the text element.
In javascript:
for (var i = 0; i < level; i++) {
$el.append('<span class="indent"></span>');
}
$el.append('Some text value');
I can't seem to get this, or anything similar to work in a JSX React.js component. Instead I had to do the following, first building a temp array to the correct length and then looping the array.
React.js
render: function() {
var tmp = [];
for (var i = 0; i < this.props.level; i++) {
tmp.push(i);
}
var indents = tmp.map(function (i) {
return (
<span className='indent'></span>
);
});
return (
...
{indents}
"Some text value"
...
);
}
Surely this can't be the best, or only way to achieve this? What am I missing?
Updated: As of React > 0.16
Render method does not necessarily have to return a single element. An array can also be returned.
var indents = [];
for (var i = 0; i < this.props.level; i++) {
indents.push(<span className='indent' key={i}></span>);
}
return indents;
OR
return this.props.level.map((item, index) => (
<span className="indent" key={index}>
{index}
</span>
));
Docs here explaining about JSX children
OLD:
You can use one loop instead
var indents = [];
for (var i = 0; i < this.props.level; i++) {
indents.push(<span className='indent' key={i}></span>);
}
return (
<div>
{indents}
"Some text value"
</div>
);
You can also use .map and fancy es6
return (
<div>
{this.props.level.map((item, index) => (
<span className='indent' key={index} />
))}
"Some text value"
</div>
);
Also, you have to wrap the return value in a container. I used div in the above example
As the docs say here
Currently, in a component's render, you can only return one node; if you have, say, a list of divs to return, you must wrap your components within a div, span or any other component.
Here is more functional example with some ES6 features:
'use strict';
const React = require('react');
function renderArticles(articles) {
if (articles.length > 0) {
return articles.map((article, index) => (
<Article key={index} article={article} />
));
}
else return [];
}
const Article = ({article}) => {
return (
<article key={article.id}>
<a href={article.link}>{article.title}</a>
<p>{article.description}</p>
</article>
);
};
const Articles = React.createClass({
render() {
const articles = renderArticles(this.props.articles);
return (
<section>
{ articles }
</section>
);
}
});
module.exports = Articles;
Array.from() takes an iterable object to convert to an array and an optional map function. You could create an object with a .length property as follows:
return Array.from({length: this.props.level}, (item, index) =>
<span className="indent" key={index}></span>
);
I'm using Object.keys(chars).map(...) to loop in render
// chars = {a:true, b:false, ..., z:false}
render() {
return (
<div>
{chars && Object.keys(chars).map(function(char, idx) {
return <span key={idx}>{char}</span>;
}.bind(this))}
"Some text value"
</div>
);
}
You can still use map if you can afford to create a makeshift array:
{
new Array(this.props.level).fill(0).map((_, index) => (
<span className='indent' key={index}></span>
))
}
This works because new Array(n).fill(x) creates an array of size n filled with x, which can then aid map.
I think this is the easiest way to loop in react js
<ul>
{yourarray.map((item)=><li>{item}</li>)}
</ul>