Infinite loop when using array map - reactjs

Since I added
const dataList = dataSet.map(element => {
return <div>{element}</div>;
});
It has gone into an infinite loop but this line is also necessary for my program to display the notes so what can I do?
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import Navbar from './Navbar';
function Notes() {
//This is just a string because we are sending just one
//might have to make it an array at some point
const [notes, setNote] = useState(String);
var dataArr = [];
const [dataSet, setDataSet] = useState([]);
const [dataList, setDataList] = useState();
useEffect(() => {
console.log('Use Effect Notes.js');
axios
.post('/api/user/notes')
.then(res => {
dataArr = res.data[0].notes;
//console.log(dataArr) ;
console.log(dataArr);
setDataSet(res.data[0].notes);
})
.catch(err => {
console.log('it didnt work' + err);
});
console.log('The array that i got ');
});
const dataList = dataSet.map(element => {
return <div>{element}</div>;
});
/*const taskList = task.map((object , index)=>{
return <div className='row justify-content-center'>
<h2>{object}</h2>
</div>
});*/
function noteDown(event) {
event.preventDefault();
var newNote = {
notes: notes,
};
console.log('note down ' + newNote);
axios
.post('/api/user/notes/add', newNote)
.then(res => {
console.log(res);
})
.catch(err => {
console.log(err);
});
}
function ffswork() {
console.log('ffswork');
}
return (
<div>
<div>
<h1> Notes </h1>
</div>
<form onSubmit={noteDown}>
<input
type="text"
placeholder="Note"
className="form-control"
value={notes}
onChange={e => {
setNote(e.target.value);
}}
/>
<input type="submit" value="AddNote" className="btn btn-primary" />
</form>
<button className="btn btn-primary" onClick={ffswork}>
getData
</button>
</div>
);
}
export default Notes;

I think you forgot to pass the array in the second argument from useEffect.
useEffect(() =>{
console.log("Use Effect Notes.js");
axios.post('/api/user/notes' ).then(res=>{
dataArr = res.data[0].notes ;
//console.log(dataArr) ;
console.log(dataArr);
setDataSet(res.data[0].notes);
}).catch(err=>{
console.log('it didnt work' + err);
});
console.log("The array that i got ");
}, []) // at this line
You can tell React to skip applying an effect if certain values
haven’t changed between re-renders.
So, if you want to make the request to get your data just once just after render, you just need to pass an empty array in the second argument of useEffect.
Read More here.

Related

Fetching and rendering a component in a loop

I have a string array that contains usernames. I want to fetch those users in a loop, put them in an array and render a component for each user. I can retrieve the data from API and print it on console but the code below gives me a white screen.
Here I'm trying to fetch data one by one using fav_list array than contains usernames. Then I want to send the data to another component called InfluencerFavoritesCard and render them. Where I'm doing wrong?
import React from 'react'
import InfluencerFavoritesCard from '../components/influencerFavoritesCard/InfluencerFavoritesCard';
import "./indexPages.css"
import "./favorites.css"
const fav_list = ["cagritaner", "acunilicali", "neslihanatagul"];
async function ListFavorites() {
let array = new Array;
var fetches = [];
for (let i = 0; i < fav_list.length; i++) {
console.log(fav_list[i]);
let uname = fav_list[i];
fetches.push(
fetch(`http://localhost:3001/api/users/${uname}`)
.then(res => {return res.text(); })
.then(res => {
array.push(res);
console.log(res);
}
)
);
}
Promise.all(fetches).then(function () {
console.log(fetches);
console.log(array[0]);
console.log(array.length);
});
return (
<div>
{array.map(item => (< InfluencerFavoritesCard infCard = { item } /> ))}
</div>
);
}
function Favorites() {
return (
<div className='favoritesMain'>
<div className='favoritesTitle-div'>
<h3 className="favoritesTitle">FAVORİLER</h3>
</div>
<div className='favoritesContainer-wrapper'>
<div className='favoritesContainer'>
{<ListFavorites/>}
</div>
</div>
<div className='favoritesFooter'></div>
</div>
);
}
export default Favorites
InfluencerFavoritesCard.jsx
import React from 'react';
import './influencerFavoritesCard.css';
const InfluencerFavoritesCard = ({ infCard }) => {
return (
<div className='infCard'>
<div className='infCard-text-info' >
<div className='infCard-name'>
<h3>{infCard.name}</h3>
</div>
<div className='infCard-username'>
<h4>{infCard.username}</h4>
</div>
<div className='infCard-categories'>
<h4>{infCard.categories}</h4>
</div>
</div>
</div>
)
}
export default InfluencerFavoritesCard;
================================
UPDATED:
I have updated the parent component like below.
export function Favorites() {
const [users, setUsers] = useState([]);
const fav_list = ["cagritaner", "acunilicali", "neslihanatagul"];
useEffect(() => {
const tempUsersCollection = [];
fav_list.map((x, i) => {
fetch(`http://localhost:3001/api/users/${x}`)
.then(res => {return res.json(); })
.then(res => {
tempUsersCollection.push(res.data.tour);
console.log(res);
}
);
});
console.log(tempUsersCollection);
setUsers(tempUsersCollection);
}, []);
return (
<div className='favoritesMain'>
<div className='favoritesTitle-div'>
<h3 className="favoritesTitle">FAVORİLER</h3>
</div>
<div className='favoritesContainer-wrapper'>
<div className='favoritesContainer'>
{users.map((item, index) => (
<InfluencerFavoritesCard
infCard={item}
key={`influencer-${item.username}-${index}`}
/>
))}
</div>
</div>
<div className='favoritesFooter'></div>
</div>
);
}
output of the console.log(tempUsersCollection) (AFTER res.json() !! )
Array []
​
0: Object { username: "neslihanatagul", biography: "contact#neslihanatagul.com", profile_picture_url: "https://scontent.fist4-1.fna.fbcdn.net/v/t51.2885-15/5787050…0_AfDfN8wtsD18vya_aLLw6M4UpP8Xx16jb9b4Hsh6cJ3wjA&oe=63A713AF", … }
​
1: Object { username: "cagritaner", biography: "Hüzünlü Bir Ponçik ve Erkeklerin İç Sesi kitaplarının yazarı.\niş birlikleri için; #goygoynetworkinfo", profile_picture_url: "https://scontent.fist4-1.fna.fbcdn.net/v/t51.2885-15/4424442…0_AfCXmyBbUrKQjpzsGEHNEMHXoNP7HZ8UKaYXHAL4S0DFlA&oe=63A6926B", … }
​
2: Object { username: "acunilicali", biography: "Acun Ilıcalı Resmi Instagram Hesabıdır.", profile_picture_url: "https://scontent.fist4-1.fna.fbcdn.net/v/t51.2885-15/1193932…0_AfDuL0toVKsSGOrnXWISBMAR78G79QwNfxJmm5cNFBuW2A&oe=63A78969", … }
​
length: 3
​
<prototype>: Array []
Favorites.jsx:136
The thing is that return statement is being rendered before all the promises are resolved, meaning that it's empty. This is exactly how it should work, so no bug here.
What you need to do is as other mentioned, use useState and useEffect to control the data:
// This will hold your collection
const [users, setUsers] = useState([])
...
// And here you need to update that collection
useEffect(()=>{
const temp = []
fetch(`http://localhost:3001/api/users/${uname}`)
.then(res => {return res.text(); })
.then(res => {
temp.push(res);
}
)
setUsers(temp)
}, [])
Later on the return you can do this:
// This controls if there are no users
if(users.length <= 0){
return <>There are no users</>
}
return (
<div className='favoritesMain'>
<div className='favoritesTitle-div'>
<h3 className="favoritesTitle">FAVORİLER</h3>
</div>
<div className='favoritesContainer-wrapper'>
<div className='favoritesContainer'>
{users.map(item => (<InfluencerFavoritesCard infCard = { item } /> ))}
</div>
</div>
<div className='favoritesFooter'></div>
</div>
);
Created a CodeSandbox so you can see this working
UPDATE:
// Call the function after the first render
useEffect(() => {
fetchUsers();
}, []);
// Wrapped all the calls in a Promise.all and update the state
async function fetchUsers() {
const response = await Promise.all(
fav_list.map((x) =>
fetch(`https://pokeapi.co/api/v2/pokemon/${x}`)
.then((res) => res.json())
.then((user) => user)
)
);
console.log(response);
setUsers(response);
}
// Handle the case where nothing is retrieved
if (users.length <= 0) {
return <>There are no users</>;
}
To fetch data asynchronously, you need to use the useEffect hook. You should store the data using a useState hook and then set that data when you get a response from your fetch request.

Text field should only change for one value and not over the entire list

I have a list and this list has several elements and I iterate over the list. For each list I display two buttons and an input field.
Now I have the following problem: as soon as I write something in a text field, the same value is also entered in the other text fields. However, I only want to change a value in one text field, so the others should not receive this value.
How can I make it so that one text field is for one element and when I write something in this text field, it is not for all the other elements as well?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function Training({ teamid }) {
const [isTrainingExisting, setIsTrainingExisting] = useState(false);
const [trainingData, setTrainingData] = useState([]);
const [addTraining, setAddTraining] = useState(false);
const [day, setDay] = useState('');
const [from, setFrom] = useState('');
const [until, setUntil] = useState('');
const getTrainingData = () => {
axios
.get(`${process.env.REACT_APP_API_URL}/team/team_training-${teamid}`,
)
.then((res) => {
if (res.status === 200) {
if (typeof res.data !== 'undefined' && res.data.length > 0) {
// the array is defined and has at least one element
setIsTrainingExisting(true)
setTrainingData(res.data)
}
else {
setIsTrainingExisting(false)
}
}
})
.catch((error) => {
//console.log(error);
});
}
useEffect(() => {
getTrainingData();
}, []);
const deleteTraining = (id) => {
axios
.delete(`${process.env.REACT_APP_API_URL}/team/delete/team_training-${teamid}`,
{ data: { trainingsid: `${id}` } })
.then((res) => {
if (res.status === 200) {
var myArray = trainingData.filter(function (obj) {
return obj.trainingsid !== id;
});
//console.log(myArray)
setTrainingData(() => [...myArray]);
}
})
.catch((error) => {
console.log(error);
});
}
const addNewTraining = () => {
setAddTraining(true);
}
const addTrainingNew = () => {
axios
.post(`${process.env.REACT_APP_API_URL}/team/add/team_training-${teamid}`,
{ von: `${from}`, bis: `${until}`, tag: `${day}` })
.then((res) => {
if (res.status === 200) {
setAddTraining(false)
const newTraining = {
trainingsid: res.data,
mannschaftsid: teamid,
von: `${from}`,
bis: `${until}`,
tag: `${day}`
}
setTrainingData(() => [...trainingData, newTraining]);
//console.log(trainingData)
}
})
.catch((error) => {
console.log(error);
});
}
const [editing, setEditing] = useState(null);
const editingTraining = (id) => {
//console.log(id)
setEditing(id);
};
const updateTraining = (trainingsid) => {
}
return (
<div>
{trainingData.map((d, i) => (
<div key={i}>
Trainingszeiten
<input class="input is-normal" type="text" key={ d.trainingsid } value={day} placeholder="Wochentag" onChange={event => setDay(event.target.value)} readOnly={false}></input>
{d.tag} - {d.von} bis {d.bis} Uhr
<button className="button is-danger" onClick={() => deleteTraining(d.trainingsid)}>Löschen</button>
{editing === d.trainingsid ? (
<button className="button is-success" onClick={() => { editingTraining(null); updateTraining(d.trainingsid); }}>Save</button>
) : (
<button className="button is-info" onClick={() => editingTraining(d.trainingsid)}>Edit</button>
)}
<br />
</div>
))}
)
}
export default Training
The reason you see all fields changing is because when you build the input elements while using .map you are probably assigning the same onChange event and using the same state value to provide the value for the input element.
You should correctly manage this information and isolate the elements from their handlers. There are several ways to efficiently manage this with help of either useReducer or some other paradigm of your choice. I will provide a simple example showing the issue vs no issue with a controlled approach,
This is what I suspect you are doing, and this will show the issue. AS you can see, here I use the val to set the value of <input/> and that happens repeatedly for both the items for which we are building the elements,
const dataSource = [{id: '1', value: 'val1'}, {id: '2', value: 'val2'}]
export default function App() {
const [val, setVal]= useState('');
const onTextChange = (event) => {
setVal(event.target.value);
}
return (
<div className="App">
{dataSource.map(x => {
return (
<div key={x.id}>
<input type="text" value={val} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
This is how you would go about it.
export default function App() {
const [data, setData]= useState(dataSource);
const onTextChange = (event) => {
const id = String(event.target.dataset.id);
const val = String(event.target.value);
const match = data.find(x => x.id === id);
const updatedItem = {...match, value: val};
if(match && val){
const updatedArrayData = [...data.filter(x => x.id !== id), updatedItem];
const sortedData = updatedArrayData.sort((a, b) => Number(a.id) - Number(b.id));
console.log(sortedData);
setData(sortedData); // sorting to retain order of elements or else they will jump around
}
}
return (
<div className="App">
{data.map(x => {
return (
<div key={x.id}>
<input data-id={x.id} type="text" value={x.value} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
What im doing here is, finding a way to map an element to its own with the help of an identifier. I have used the data-id attribute for it. I use this value again in the callback to identify the match, update it correctly and update the state again so the re render shows correct values.

Fetch data before component render, Reactjs

I am having a slight issue with my react code. I want the data to be completely fetched before rendering. However, I have tried various ways such as 'setGroupLoaded to true' after the async call, but it is still not working. When the component first mounts, 'groupLoaded == false and myGroup == [],', then it goes to the next conditional statement where 'groupLoaded == true' but the myGroup [] is still empty. I was expecting the myGroup [] to be filled with data since groupLoaded is true. Please I need help with it.
function CreateGroup({ currentUser }) {
const [name, setName] = useState("");
const [myGroup, setMyGroup] = useState([]);
const [groupAdded, setGroupAdded] = useState(false);
const [groupLoaded, setGroupLoaded] = useState(false);
const handleChange = (e) => {
const { value, name } = e.target;
setName({
[name]: value,
});
};
useEffect(() => {
const fetchGroupCreated = async () => {
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
};
fetchGroupCreated();
setGroupLoaded(true);
return () => {
setMyGroup([]);
};
}, [currentUser.id, groupAdded, deleteGroupStatus]);
useEffect(() => {
let groupId = myGroup.length ? myGroup[0].id : "";
let groupName = myGroup.length ? myGroup[0].groupName : "";
if (myGroup.length) {
JoinGroup(currentUser, groupId, groupName);
setTimeout(() => fetchGroupMembers(), 3000);
}
}, [myGroup]);
let itemsToRender;
if (groupLoaded && myGroup.length) {
itemsToRender = myGroup.map((item) => {
return <Group key={item.id} item={item} deleteGroup={deleteGroup} />;
});
} else if (groupLoaded && myGroup.length === 0) {
itemsToRender = (
<div>
<form onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="exampleInputTitle">Group Name</label>
<input
type="text"
className="form-control"
name="name"
id="name"
aria-describedby="TitleHelp"
onChange={handleChange}
/>
</div>
<button type="submit" className="btn btn-primary">
Add group{" "}
</button>
</form>
</div>
);
}
return (
<div>
<h3>{currentUser ? currentUser.displayName : ""}</h3>
{itemsToRender}
</div>
);
}
The problem is here:
useEffect(() => {
const fetchGroupCreated = async () => {
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
};
fetchGroupCreated();
setGroupLoaded(true);
return () => {
setMyGroup([]);
};
}, [currentUser.id, groupAdded, deleteGroupStatus]);
When you call setGroupLoaded(true), the first call to setMyGroup(groupArr) hasn't happened yet because fetchMyGroup(currentUser) is asynchronous. If you've never done this before, I highly recommend putting in some logging statements, to see the order in which is executes:
useEffect(() => {
const fetchGroupCreated = async () => {
console.log("Got data")
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
};
console.log("Before starting to load")
fetchGroupCreated();
setGroupLoaded(true);
console.log("After starting to load")
return () => {
setMyGroup([]);
};
}, [currentUser.id, groupAdded, deleteGroupStatus]);
The output will be:
Before starting to load
After starting to load
Got data
This is probably not the order you expected, but explains exactly why you get groupLoaded == true, but the myGroup is still empty.
The solution (as Nick commented) is to move the setGroupLoaded(true) into the callback, so that it runs after the data is retrieved:
const fetchGroupCreated = async () => {
let groupArr = await fetchMyGroup(currentUser);
setMyGroup(groupArr);
setGroupLoaded(true);
};
fetchGroupCreated();
An alternative approach may be to await the call to fetchGroupCreated(). I'm not sure if it'll work, but if it does it'll be simpler:
await fetchGroupCreated();

My code is showing error when search is not found with React

Please am new to React working with API, when ever the search object is not found, it returned error! Please help me.
I have tried using techniques that I know but it's not working.
What did i need to do or add to make it work and return non-error when search is not returned.
function Search() {
const[searchQuery, setSearchQuery] = useState('');
const[advices, setAdvices] = useState({ slips: [] });
const text = 'Tweet';
const shareText = 'Check https://adviser.surg.sh for more...';
const onInputChange = (e) => {
setSearchQuery(e.target.value);
}
let API_URL = `https://api.adviceslip.com/advice/search/`;
const fetchAdvices = async () => {
const result = await axios.get(`${API_URL}${searchQuery}`);
console.log(result.data);
setAdvices(result.data);
}
const onSubmitHandler = (e) => {
e.preventDefault();
fetchAdvices();
}
return (
<div>
<section>
<form className='search-box' onSubmit={onSubmitHandler}>
<input
type='search'
placeholder='Search for advice e.g love'
value={searchQuery}
onChange={onInputChange}
/>
<button className='search-btn' type='submit'>Search</button>
</form>
<div className='adviceList'>
{
advices.slips.map((advice, id) => {
return (
<div key={id}>
<div className='advice-card'>
<h2>{advice.advice}</h2>
<p> <Twitter text={text}
url={advice.advice}
shareText={shareText} />
</p>
</div>
</div>
);
})
}
</div>
</section>
</div>
)
}
export default Search
How can i solve this, please help me with what to do.
The most optimal solution is to have the backend to return some sort of data even when the result is not found (ex. a found property). Then, use that data to check if there is a result or not. Otherwise, you'd have to do error handling:
const fetchAdvices = async () => {
try {
const result = await axios.get(`${API_URL}${searchQuery}`);
console.log(result.data);
setAdvices(result.data);
} catch(e) {
setAdvices({slips: []}); //or any other value you want to set it to when there's no result returned
}
}
Rather than using await, try this;
const fetchAdvices = axios.get(`${API_URL}${searchQuery}`)
.then(result => {
console.log(result.data);
setAdvices(result.data);
}).catch(error => {
console.log(error);
// handleError(error);
});

React: updating state after deletion

I'm trying to update elements after deletion, without refreshing a page. Currently, if delete a record, need to refresh a page to see the result. As I understand, need to update useState, but I do not understand how to do it. If I loop useEffect it works but slowly, but I think it's not the best idea to loop get response.
Get all records from a database.
const PostsGetUtill = () => {
const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
setPosts(response.data);
}).catch(function (error) {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log(error.config);
});
};
useEffect(() => {
fetchPosts();
}, []); // }, [fetchPosts]); <--- working well with loop
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
Sort and map records
export default function PostMansonry({ posts, columns }) {
return (
<section className="masonry" style={{ gridTemplateColumns: `repeat(${columns}, minmax(275px, 1fr))` }}>
{posts.sort((a, b) => a.zonedDateTime < b.zonedDateTime ? 1 : -1).map((posts, index) =>
<MasonryPost {...{ posts, index, key: index }} />)
}
</section>
)
}
Put data to the card
export default function MasonryPost({ posts, index }) {
return (
<div key={index} className="masonry-post">
<div className="card">
<div className="card-body">
<h5 className="card-title">{posts.title}</h5>
<p className="card-text">{posts.description}</p>
<p className="card-text"><small className="text-muted"> {posts.zonedDateTime}</small></p>
<div><button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id)} className="btn btn-danger">Delete</button></div>
</div>
</div>
</div>
)
}
Deleting
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
console.log(response);
}).catch((error) => {
if (error.response) {
console.log(error.response.data);
console.log(error.response.status);
console.log(error.response.headers);
} else if (error.request) {
console.log(error.request);
} else {
console.log('Error', error.message);
}
console.log('error config', error.config);
});
};
export default PostsDeleteUtill;
Basically what you need to do is, in your PostsDeleteUtill function, in the promise return of your axios.delete, you need to update your posts state, which is set in PostsGetUtill.
In order to do that, you have 2 options:
Use a global state (React Context, Redux, etc)
Pass your setPosts handle all the way to your PostsDeleteUtill
I think option 1 is a bit cleaner for your specific case, but if you don't need global state anywhere else in your project, maybe it is fine to have a not so clean solution instead of implementing the whole global state structure for only one thing.
Option 1 pseudo code:
Your PostsGetUtill component would use a global state instead of local state:
const PostsGetUtill = () => {
// Remove this:
// const [posts, setPosts] = useState([]);
const fetchPosts = () => {
axios.get("api/v1.0/post/get").then(response => {
console.log(response.data);
// Instead of a local "setPosts" you would have a global
// "setPosts" (in Redux, this would be a dispatch)
dispatch({type: "PUT_POSTS", action: response.data})
}).catch(function (error) {
// No changes here...
});
};
// This runs only the first time you load this component
useEffect(() => {
fetchPosts();
}, []);
// Use your global state here as well:
return (
<section className="container-post">
<PostMansonry posts={globalState.posts} columns={3} />
</section>
);
};
export default PostsGetUtill;
In your PostsDeleteUtill function:
const PostsDeleteUtill = async (post_Id) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
// Update global state here. Probably filter the data to remove
// the deleted record
const updatedPosts = globalState.posts.filter(post => post.id !== response.data.id)
}).catch((error) => {
// No changes here
});
};
export default PostsDeleteUtill;
Option 2 pseudo code:
In your PostsGetUtill component, create and pass on a handleRemovePost:
// Your existing code ...
const handleRemovePost = (postID) => {
const filteredPosts = posts.filter(post => post.id !=== postID)
setPosts(filteredPosts)
}
return (
<section className="container-post">
<PostMansonry posts={posts} columns={3} handleRemovePost={handleRemovePost} />
</section>
);
In your PostMansonry, pass on again your handleRemovePost
export default function PostMansonry({ posts, columns, handleRemovePost }) {
return (
// Your existing code ...
<MasonryPost {...{ posts, index, key: index, handleRemovePost }} />)
)
}
Again in your MasonryPost
export default function MasonryPost({ posts, index, handleRemovePost }) {
return (
// Your existing code ...
<button type="button" onClick={(e) => PostsDeleteUtill(posts.post_Id, handleRemovePost)} className="btn btn-danger">Delete</button>
)
}
And finally:
const PostsDeleteUtill = async (post_Id, handleRemovePost) => {
axios.delete(`api/v1.0/post/delete/${post_Id}`).then(response => {
handleRemovePost(response);
})
};
PS: Please note that I only added a pseudo-code as a reference, trying to point out specific parts of the code that needs to be updated. If you need more information about global state, you can check React Context and Redux

Resources