Loop through array in react onclick - javascript

I have an array of objects. How can I loop through this array on click?
test = ['a', 'b', 'c', 'd']
{this.state.test.map(function(i){
return <span> {i} </span>
})}
I would normally loop through like this in react but this prints them all out at once. How can I display 'a' on the page and then the next time i click it, I display 'b' so on until the end of the array?
I need a function that can tell where I am in the array and display that on the page at the right moment

You could implement e.g. a counter variable and use Array#slice to show specified amount of elements inside the test array.
Codesandbox link
import React from "react";
export default class Hello extends React.Component {
state = {
test: ["a", "b", "c", "d"],
index: 0
};
handleClick = () => {
let i = this.state.index < this.state.test.length ? this.state.index += 1 : 0;
this.setState({ index: i });
};
render() {
return (
<div>
{this.state.test.slice(0, this.state.index).map(v => {
return (
<span>{v}</span>
);
})}
<button onClick={this.handleClick}>Click</button>
</div>
);
}
}
Edit: I was playing with it and actually I got even a better solution, which allows you to avoid re-looping Array#map on every render, with little help of hidden attribute. Also we are getting rid of the Array#slice function.
Improved codesandbox link
app.js
{this.state.test.map((v, i) => {
return (
<span hidden={i >= this.state.index}>{v}</span>
);
})}
And the Span.js component:
<span hidden={this.props.hidden}>
{this.props.v}
</span>

set initial index at your state first, then use that index as index array in span. next make some function to handle the increment, simply put it like this
const switchNinjaHandler = () => {
let looper = ++this.state.index % this.state.test.length;
setState({
...personsState, // WE SHOULD ALWAYS GIVE THE EXCACT INITIAL STATE VALUE, THIS IS WHERE SPREAD COMING HANDY
this.index : looper,
});
to display
return ( <div> {this.state.test[this.index]} </div> )

Related

Too many re-renders useState React js

I want to display objects from the array one by one by clicking on the card.
To do this, I made a check for array length (props.tasks.length), and if it is more than 1, then the array is shuffled and then I want to display these objects one by one on click.
But after a click, a new array is generated each time and often the objects are repeated 2-4 times.
But I get an error: Too many re-renders. React limits the number of renders to prevent an infinite loop.
function App(props) {
let [random, setRandom] = useState({});
let [newArr, setNewArr] = useState(props.tasks.sort(() => Math.random() - 0.5));
let i = 0;
random = newArr[i]
setRandom(newArr[i])
const randomCard =()=> {
console.log(random);
console.log(i);
console.log(newArr[0],newArr[1],newArr[2],newArr[3], newArr[4], newArr[5]);
console.log(newArr[i]);
if (i <= newArr.length) {
i++
} else {
newArr = props.tasks.sort(() => Math.random() - 0.5);
console.log('clean');
i=0
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{random.name}</p>
<p>{random.translate}</p>
<p>{random.note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;
The main culprit in your code example is
setRandom(newArr[i])
Using a hook's set method should only be done via an action or wrapped in a useEffect for side effects. In your code, setNewArr is called on every rerender and will cause the component to rerender... causing the infinite loop of rerenders.
You also don't need to store the selected element from your array in a state, you're essentially doing this by just storying the index in the use state.
Your solution should look something like this.
Also you want to reset i when it's < newArr and not <= newArr because if your array is of length "2" and i is "2" then you're setting i to "3" which doesn't point to any element in your list.
const shuffle = (arr) => arr.sort(() => Math.random() - 0.5);
function App(props) {
// Default empty list
const [newArr, setNewArr] = useState([]);
// Sets the default index to 0
const [i, setI] = useState(0);
// We don't want to call shuffle on every rerender, so only setNewArr
// Once when the component is mounted.
useEffect(() => {
setNewArr(shuffle(props.tasks));
}, []);
const randomCard =()=> {
if (i < newArr.length) {
setI(i + 1);
} else {
setNewArr(shuffle(props.tasks));
setI(0);
}
}
return (
<div>
{newArr.length > 1 ? (
<div className="item" onClick={randomCard}>
<p>{newArr[i].name}</p>
<p>{newArr[i].translate}</p>
<p>{newArr[i].note}</p>
</div>
) : (
<p>Nothing</p>
)}
</div>
);
}
export default App;

Custom function I made that returns <span> elements is getting "Warning: Each child in a list should have a unique 'key' prop"

I am trying to create a function that takes an array of words such as:
const words = ['hello', 'my', 'name']
and returns them in the form:
<>
<span>hello</span> // <span>my</span> // <span>name</span>
</>
This is the react component I created to do this:
function StyleWords({words, ...props}){
return(
words.map((word, index, words) => {
if(index != words.length-1){
return <><span key={word} className='highlight'>{word}</span> // </>
}
else{
return <span key={word} className='highlight'>{word}</span>
}
})
)
}
and then call it like so:
<div><StyledWords words={['hello', 'my', 'name']} /></div>
Now this does work but I get the warning about keys. So my thinking is that either I have done this inappropriately or that I have missed something out with the keys. Any help?
You need to provide the key to the component which is the root of the item in the list.
function StyleWords({words, ...props}){
return(
words.map((word, index, words) => {
if(index != words.length-1){
return <React.Fragment key={word}>
<span className='highlight'>{word}</span> //
</React.Fragment>
}
else{
return <span key={word} className='highlight'>{word}</span>
}
})
)
}
As Lokesh suggested, you should use a unique value as key for the items instead of using word if it is not guaranteed that it will be unique.

Remove element from useState array by index

SOLUTION: Update the key value for the input element to refresh the default value => content of the input element. Deleting an element from the array DID work. Thanks for your help!
src: https://thewebdev.info/2022/05/12/how-to-fix-react-input-defaultvalue-doesnt-update-with-state-with-javascript/#:~:text=state%20with%20JavaScript%3F-,To%20fix%20React%20input%20defaultValue%20doesn't%20update%20with%20state,default%20value%20of%20the%20input.
I got an useState array in my code which represents a lisst of students:
const [students, setStudents] = useState([""]);
This array gets mapped to student elements:
{students.map((student, index) => <Student setStudents={setStudents} students={students} id={index} key={index} content={student} />)} I also got an AddStudent element which adds students to the array.
function AddStudent(props) {
const {setStudents} = props;
return (
<button className="change-student add-student" onClick={() => {
setStudents((students) => [...students, ""])
}}>
+
</button>
);
}
The RemoveStudent component is supposed to remove a student by its index in the array. I've tried many different ways but none worked correctly. How can I get it to work? Here is my code:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button className="change-student remove-student" onClick={() => {
let data = students;
if(id > -1) {
data.splice(id, 1);
}
console.log(data)
// setStudents(data)
// alternative:
// setStudents(students.filter(index => index !== id)); // removes the last element in the list
// doesn't work properly
}}>
-
</button>
)
}
Thanks for your help!
2 things should be noted here:
While updating react state arrays, use methods that return a new array (map, filter, slice, concat),
rather than ones that modify the existing array (splice, push, pop, sort).
While updating React state using its previous value, the callback argument should be used for the state setter. Otherwise you may get stale values. (See React docs).
if(id > -1) {
setStudents(students=> students.filter((s,i)=>(i != id)))
}
Consult this article, for a complete reference about how to update React state arrays.
You need to copy the students array first and then try removing the student by index. I assume by id you mean index at which to remove the student. Then you can try something like:
function RemoveStudent(props) {
const {students, setStudents, id} = props;
return (
<button
className="change-student remove-student"
onClick={() => {
if(id > -1) {
const data = [...students]; // making a copy
data.splice(id, 1); // removing at index id
console.log(data)
setStudents(data)
}
}}
>
-
</button>
)
}
With array.filter() you have a mistake in how you pass callback to filter() method. Please try the following:
setStudents(students.filter((,index) => index !== id));
Notice the index is second param of the callback so I used a , before index.
After #Irfanullah Jan 's answer you should make sure how you show the student.
Here is the simple example:
const [students, setStudents] = useState([1, 2, 3]);
return (
<div>
{students.map((student, index) => {
return <div>{student}</div>; // show the value not the index
})}
<button
onClick={() => {
let id = 1;
const copy = [...students];
copy.splice(id, 1)
console.log(copy)
setStudents(copy);
}}
>
-
</button>
</div>
);
The code above will delete the student of "index==1"

React ternary operator used to iterate through a results array (otherwise undefined), is there a different, preferred approach?

I'm new to React and my current code works, but is there a better, "React way" (w/o ternary) of mapping through an empty array, before it has the results (without returning undefined)?
I'm rendering a materialize card for every result returned from an API search. What the code does isn't the issue because it works w/ my current solution.
Thanks in advance.
class NutritonixResultsDisplay extends Component {
render() {
const hits = this.props.nutritionixResults.hits;
return (
<div className='nutritionixResultsDiv'>
{hits
? hits.map((result, i) => (
<div key={i} className='nutritionixResultDiv'>
<Card className='cardDisplay' header={<CardTitle reveal image='' waves='light'/>}
title={result.fields.item_name}reveal={<div><p>{`Calories: ${result.fields.nf_calories}`}</p><p>{`Protein: ${result.fields.nf_protein}`}</p></div>}> <p>This is a link</p>
<p>{`Brand Name: ${result.fields.brand_name}`}</p>
</Card>
</div>
))
: ''}
</div>
);
}
}
export default NutritonixResultsDisplay;
As an alternative you can use || []:
(hits || []).map( // ... etc. )
Just simply make hits an empty array.
const hits = this.props.nutritionixResults.hits || [];

Rendering React Components from Array of Objects

I have some data called stations which is an array containing objects.
stations : [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
]
I'd like to render a ui component for each array position. So far I can write
var stationsArr = []
for (var i = 0; i < this.data.stations.length; i++) {
stationsArr.push(
<div className="station">
{this.data}
</div>
)
}
And then render
render(){
return (
{stationsArr}
)
}
The problem is I'm getting all of the data printing out. I instead want to just show a key like {this.data.call} but that prints nothing.
How can I loop through this data and return a new UI element for each position of the array?
You can map the list of stations to ReactElements.
With React >= 16, it is possible to return multiple elements from the same component without needing an extra html element wrapper. Since 16.2, there is a new syntax <> to create fragments. If this does not work or is not supported by your IDE, you can use <React.Fragment> instead. Between 16.0 and 16.2, you can use a very simple polyfill for fragments.
Try the following
// Modern syntax >= React 16.2.0
const Test = ({stations}) => (
<>
{stations.map(station => (
<div key={station.call} className='station'>{station.call}</div>
))}
</>
);
// Modern syntax < React 16.2.0
// You need to wrap in an extra element like div here
const Test = ({stations}) => (
<div>
{stations.map(station => (
<div className="station" key={station.call}>{station.call}</div>
))}
</div>
);
// old syntax
var Test = React.createClass({
render: function() {
var stationComponents = this.props.stations.map(function(station) {
return <div className="station" key={station.call}>{station.call}</div>;
});
return <div>{stationComponents}</div>;
}
});
var stations = [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
];
ReactDOM.render(
<div>
<Test stations={stations} />
</div>,
document.getElementById('container')
);
Don't forget the key attribute!
https://jsfiddle.net/69z2wepo/14377/
I have an answer that might be a bit less confusing for newbies like myself. You can just use map within the components render method.
render () {
return (
<div>
{stations.map(station => <div key={station}> {station} </div>)}
</div>
);
}
this.data presumably contains all the data, so you would need to do something like this:
var stations = [];
var stationData = this.data.stations;
for (var i = 0; i < stationData.length; i++) {
stations.push(
<div key={stationData[i].call} className="station">
Call: {stationData[i].call}, Freq: {stationData[i].frequency}
</div>
)
}
render() {
return (
<div className="stations">{stations}</div>
)
}
Or you can use map and arrow functions if you're using ES6:
const stations = this.data.stations.map(station =>
<div key={station.call} className="station">
Call: {station.call}, Freq: {station.frequency}
</div>
);
This is quite likely the simplest way to achieve what you are looking for.
In order to use this map function in this instance, we will have to pass a currentValue (always-required) parameter, as well an index (optional) parameter.
In the below example, station is our currentValue, and x is our index.
station represents the current value of the object within the array as it is iterated over.
x automatically increments; increasing by one each time a new object is mapped.
render () {
return (
<div>
{stations.map((station, x) => (
<div key={x}> {station} </div>
))}
</div>
);
}
What Thomas Valadez had answered, while it had provided the best/simplest method to render a component from an array of objects, it had failed to properly address the way in which you would assign a key during this process.
There are couple of way which can be used.
const stations = [
{call:'station one',frequency:'000'},
{call:'station two',frequency:'001'}
];
const callList = stations.map(({call}) => call)
Solution 1
<p>{callList.join(', ')}</p>
Solution 2
<ol>
{ callList && callList.map(item => <li>{item}</li>) }
</ol>
Of course there are other ways also available.

Categories

Resources