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

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()}>

Related

Why is the first element getting removed too?

So I am new to javascript and I tried making a todo list. This works well with adding elements. The issue is when I am removing some item, the first one gets removed too, why is it so? I know I am missing a small thing and this may be really basic but I am not able to find out what that is.
const App1 = () => {
const [item, updatedItem]=useState('');
const [Items, setItems]=useState([]);
function inputEvent(event) {
updatedItem(event.target.value);
}
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return[
...prev,
item
]
});
updatedItem('');
}
let key=0;
return(<>
<div className='back'>
<div className='list'>
<header>ToDo List</header>
<form onSubmit={addItem}>
<input type='text' placeholder='Add an item' value={item} onChange={inputEvent}/>
<button type='submit'>+</button>
</form>
<div className='items'>
<ol>
{Items.map((val) => <li><button id={key++} onClick={(event) => {
setItems((Items) => {
return Items.filter((val, index) => {
if(index!==Number(event.target.id)){
return index;
}
}
);
});
key=0;
}}>x</button>{val}</li>)}
</ol>
</div>
</div>
</div>
</>);
You return the index in your filter, expecting this to always be true, yet 0 (the index of the first element) is a falsy value.
Try this instead:
return Items.filter((_val, index) => index !== Number(event.target.id));
Some unrelated code-quality notes:
In React, you should always set a key prop on each element when looping through them, rather than id.
map has a second argument, index, which it passes into the callback --- you don't have to keep track of this yourself with e.g. key++ etc.
If you use map's index parameter, then you can pass that directly into your filter rather than using Number(event.target.id), which is not very idiomatic in React.
If you don't use an argument of a callback, it's a good idea to prefix it with a _ (like I've done with _val here), to make it explicit that you're not using it.
Your filter callback should return a flag. index is a number. When treated as a flag, 0 is false (more on MDN). Instead:
return Items.filter((val, index) => index !== Number(event.target.id));
However, your code is returning an array of li elements without setting key on them (see: keys), which React needs in order to manage that list properly (you should be seeing a warning about it in devtools if you're using the development version of the libs, which is best in development). You can't use the mechanism you're using now for keys when doing that, it will not work reliably (see this article linked by the React documentation). Instead, assign each Todo item a unique ID when you create it that doesn't change, and use that as the key (and as the value to look for when removing the item):
// Outside the component:
let lastId = 0;
// Inside the component:
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return [
...prev,
{text: item, id: ++lastId}
];
});
updatedItem("");
};
// Add a remove function:
const removeItem = ({currentTarget}) => {
const id = +currentTarget.getAttribute("data-id"); // Get the ID, convert string to number
setItems(items => items.filter(item => item.id !== id));
};
// When rendering:
{Items.map((item) => <li key={item.id}><button data-id={item.id} onClick={removeItem}>x</button>{item.text}</li>)}
In some cases it may be useful to use useCallback to memoize removeItem to avoid unnecessary rendering, but often that's overkill.

How can you create a Tree display using dynamic data from an API in React?

So, I've got a bit of a doozy here. I've looked into trees and the like in react, and I'm fairly confident I can implement one, given the right data structure. The problem I'm running into is that I'm getting my data from an API that doesn't, at least natively, have the structure for a tree, so I'm trying to create that structure on the fly.
This is what the data I'm getting back from the API looks like:
const category = //could have children
{
"object_name":"B2B",
"data_provider_key":"bluekai",
"object_key": "bluekai-31",
"object_type":"category",
};
const segment = //will only be a child
{
"object_name":"B2B > Role/Title > Admin Exec",
"data_provider_key":"bluekai",
"object_key": "bluekai-1145",
"object_type":"segment",
"cpm_cost":2.500
};
And this is the logic that I'm using to try and manipulate the data from the API to add children/create parents, etc.
const asyncView = async function (segTree: string | undefined) {
const categoryDataCall = api.getBeeswaxSegmentView(categoryBody);
const segmentDataCall = api.getBeeswaxSegmentView(segmentBody);
const data = await Promise.all([categoryDataCall, segmentDataCall]);
const parent = categoryData.find( (el: any) => el.object_key === segTree);
const categories = data[0].payload;
if (categories.length >= 1) {
for (let i = 0; i < categories.length; i++) {
categories[i].children = [];
}
}
parent.children = categories.concat(data[1].payload);
setCategoryData(parent.children);
setParent(parent);
}
asyncView(e.currentTarget.dataset.segment_tree);
}
return (
<>
<div>PARENT: {parent.object_name}</div>
{categoryData.length === 0
? <div>No category data</div>
: categoryData.map((e: any) => {
if (e.object_type === 'segment') {
return (
<div data-segment_tree={`${e.object_key || "NULL"}`}
data-provider_key={`${e.data_provider_key}`}
>
{`Name: ${e.object_name} (${e.object_key}, $${parseFloat(e.cpm_cost).toFixed(2)} CPM)`}
</div>
)
}
return (
<div data-segment_tree={`${e.object_key || "NULL"}`}
data-provider_key={`${e.data_provider_key}`}
onClick={getCategoryAndSegmentData}
>
{`Name: ${e.data_provider_name || e.object_name}`}
</div>
)
})
}
</>
);
}
I haven't implemented the Tree part yet, but that's because I am fairly confident I'm not creating the relations between elements correctly in my logic/the logic breaks if there are multiple 'trees'/categories on a page (which there will be.)
Sorry if this is a bit much, but any help or just ideas on dynamically modifying the data from the API to fit the tree structure of child/parent relationships would be appreciated!
Edit in response to Ray Hatfield:
What's the relationship between a category and a segment?
Segments will always be children of Categories, and will never have children of their own. Categories can have other categories as children.
How do you establish which category a segment belongs to?
The object_key property from the Category object gets passed to the API call(s) (two calls are made: one for segments, and one for categories). This is the only relation between segments and categories - nothing else in the return data ties them together.
What is e?
I assume you mean in the e.currentTarget.dataset.segment_tree line.
e is the event object, which I'm using to create the queries and firing them off on click events. I'm storing the object_key in a data-attribute in the HTML, and then passing it to a handler to generate the categoryBody and segmentBody used in the asyncView() function.
For some reason I have to explicitly pass the e.currentTarget.dataset.segment_tree as an argument to the async function even though they're in the same scope, but all it's doing is allowing me to find the Category that was clicked in the existing array of data in state.
What is categoryData?
categoryData is the array of values ( that is currently in state. So, each time I hit the API I update category data to re-render everything.
Effectively, I'm finding the parent (category that was clicked) firing off the API calls to get all the subcategories/segments associated with the clicked categories object_key, and then adding a children prop to any incoming categories, and then setting the children of the last clicked element equal to the returned segments + categories, and then rendering.
I put together this working demo on jsfiddle. Here are the highlights:
The Core Idea
The core idea is a Category component that's responsible for loading and rendering its own segments and subcategories. The subcategories get rendered using the same Category component, resulting in a recursive tree structure.
The Category Component
const Category = ({item}) => {
const [data, setData] = React.useState();
const onClick = data
? () => setData(null) // discard data (collapse) on subsequent click
: () => load(item.object_key).then(setData);
return (
<div className="category">
<div
className={`category-name ${data ? 'open' : ''}`}
onClick={onClick}
>
{item.object_name}
</div>
{data && (
<ul>
{ data.map((child, i) => (
<li key={i}><Node item={child}/></li>
))}
</ul>
)}
</div>
)
}
This component takes a single item prop representing the category. The component expects item to have object_key and object_name fields, like the category object in your example.
Initially the component has no information other than what's in the item, so it renders the category's name with an onClick handler that makes API calls to fetch the category's children and then stores the result in the component's state:
const [data, setData] = React.useState();
const onClick = () => load(item.object_key).then(setData);
On the subsequent render the Category component renders its children (segments and subcategories) in addition to the category name. Subcategories are rendered using the same Category component, resulting in a recursive tree structure.
The Segment Component
const Segment = ({item: {object_name}}) => (
<div className="segment">{object_name}</div>
);
Simple component for rendering segments. Just returns the segment name here, but you could of course expand it to do whatever you need it to do.
The Node Component
const Node = ({item}) => {
const Cmp = item.object_type === 'category' ? Category : Segment;
return <Cmp item={item} />;
};
Convenience component for rendering a <Segment /> or <Category /> for the given item according to its type.
The rest of the example code is just hand waving to simulate the API calls and generate mock data.
load function
const load = async (parentKey) => {
const [categories, segments] = await Promise.all([
mockApiRequest('category'),
mockApiRequest('segment')
]);
return [
...categories,
...segments
];
}
Given a category's object_key, this makes the api calls to get the segments and subcategories, merges and returns the results as a single array.
mockApiRequest
const mockApiRequest = (type) => (
new Promise((resolve) => {
setTimeout(() => resolve(fakeData(type)), 200);
})
)
Simulates the API request. Waits 200ms before resolving with mock data.
fakeData
// generate mock response data
const fakeData = (type) => {
// copy the list of names
const n = [...names];
// plucks a random name from the list
const getName = () => (
n.splice(Math.floor(Math.random() * n.length), 1)[0]
);
// generate and return an array of data
return Array.from(
{length: Math.floor(Math.random() * 5) + 1},
(_, i) => ({
...samples[type],
object_name: getName()
})
)
};
Generates mock category or segment data by copying the sample and choosing a random name.

How can I give a key in JSX the value of a variable depending on conditions

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.

How to Delete individual items from list in react

I have built a simple ToDo App. Rending the tasks from the form input is working fine, but I am unable to delete the tasks when clicked on Delete button.
export class TodoList extends Component {
constructor(props) {
super(props)
this.state = {
task:'',
items:[]
}
}
onChangeHandler=(e)=>{
this.setState({
[e.target.name]: e.target.value
})
}
addItem=(e)=>{
e.preventDefault()
if (this.state.task!==""){
this.setState({
items:[...this.state.items,this.state.task],
task:''
})
}
}
removeItem=(index)=>{
const remainingItems = this.state.items.filter(j => {
return j !== index
})
this.setState({
items: remainingItems
})
};
render() {
return (
<div>
<form>
<input type='text' name="task"onChange={this.onChangeHandler} value={this.state.task} placeholder='Enter Task'/>
<button type='submit' onClick={this.addItem}>Add Task</button>
</form>
<Lists items={this.state.items}
delete={this.removeItem}/>
</div>
)
}
}
export class Lists extends Component {
removeItems=(index)=>{
this.props.delete(index)
}
render() {
return (
<div>
{this.props.items.map((item,index)=>
<li className="Lists" key={index}>{item}
<button type='button' onClick={this.removeItems(index)}>Remove</button>
</li>)}
</div>
)
}
}
Do you even happen to have any items to delete here or the list comes up empty? Delete function itself looks fine but you have couple of problems here.
Don't use index as a key. In case you're reordering or deleting (which you are doing) an array of items, you can run into a lot of issues. Here's a good article: https://medium.com/#vraa/why-using-an-index-as-key-in-react-is-probably-a-bad-idea-7543de68b17c
Probably the error is with this since you're deleting key which, since it's an iterator, is reassigned to another element when array repopulates itself. Change iterator to some other unique identifier for each element.
You're calling removeItems method as soon as it's set. If you have invoked methods (with ()) inside return of render, it will be executed immediately on each refresh. That's why I'm asking do you have anything to delete at all since, if delete function is okay written, this would probably delete all items as soon as they are added.
Best method would be to use dataset. To each element you can add dataset like this:
data-item-id={some-id} and you can fetch it inside your method from the fired event like this const clickedId = event.currentTarget.dataset.someId. Note that dataset in the element must be written like-this, and it's rewritten automatically when fetching it into camelCase (likeThis). Then you can use this index to target the element you want inside the array and delete it.
Note that the iterator issue still applies, and you need a different unique identifier.
Let me know if you need further explanation.
You can delete the current item using splice method.
removeItem = index=> {
let newArray = this.state.items;
newArray.splice(index, 1);
this.setState({
items: newArray
});
};
It would be better to use onClick for removing item like this :
<button type='button' onClick={()=>this.removeItems(index)}>Remove</button>
Hope this helps.
I prefer to pass item that i would like to remove, index can be decieving becouse it changes.
Find index by unique key, i use item.id as unique key.
removeItem = item => {
const items = this.state.items;
// if using lodash i use findIndex
const index = _.findIndex(items, i => i.id === item.id)
// if plain js
const index = items.findIndex(i => i.id === item.id)
items.splice(index, 1);
this.setState({
items
});
};

How to loop and render elements in React.js without an array of objects to map?

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>

Categories