React fetch JSON data inside loop - reactjs

I need to render a list of buttons, in which I have the value coming from one JSON (an url to an object in a server, stored in a local file), and the displayed text from another JSON (the title of the object, which I can only get through a fetch function).
My local JSON looks like this. From it, I need to get the category name and the list of URLS and use them to make a category button (with the value being the list of URLS and the displayed text being the name).
{
"layerUrls": [
{"name": "Education",
"urls": [
"someurl1",
"someurl2"
]},
{"name": "Transports",
"urls": [
"someurl3",
"someurl4"
]
}]
}
Then, I need to get each URL in a button (in the div that matches its category), and I need to get the title of the object linked to each URL from the server. To do all of this, I'm looping through layerUrls to get each category and create the category button. Then, in each loop, I'm mapping the list of URLs to a series of buttons and fetching the names in the server.
generateButtons = (css) => {
// Styling
const style = css
// Get local JSON data
const data = this.props.config.layerUrls
// Init lists
const list = []
const solo = []
// Looping to create a div per category with 1 category button & multiple individual buttons
for (let i = 0; i < data.length; i++) {
let donneesCateg = data[i]
let listURLS = donneesCateg["urls"]
list.push(<div>
{/* Create category buttons */}
<button id={donneesCateg["name"]} className="categ" value={listURLS} onClick={this.selectChangeMultiple}>{donneesCateg["name"]}</button>
{/* Map individual buttons */}
{listURLS.map((item) => {
const featureLayer = new FeatureLayer({
url: item
});
fetch(item + "?f=pjson")
.then(res => res.json())
.then(data => {
const titre = (data.name).replaceAll('_', ' ').substring(1).toLowerCase()
solo.push(<button id={donneesCateg["name"]} className="btn" value={item} onClick={this.selectChangeSingle}>{titre}</button>)
})
return solo
})
}
</div>)
}
// Return the buttons
return (
<div>
<style>
{style}
</style>
{list}
</div>
)
}
The trouble comes from the fetch function I'm using to retrieve the name from the server for each object. I'm pushing the buttons to a list and returning it... Except that it doesn't return anything. I only get the category buttons and that's it. I tried with useEffect with no more success, and I tried to push the name only to the list, but I got an infinite loop.
Can anyone help ? Thanks!

fetch is an asynchronous function, i.e the rest of the code doesn't wait for it to finish executing unless explicitly instructed to do so. Instead of:
fetch(item + "?f=pjson")
.then(res => res.json())
.then(data => {
const titre = (data.name).replaceAll('_', ' ').substring(1).toLowerCase()
solo.push(<button id={donneesCateg["name"]} className="btn" value={item} onClick={this.selectChangeSingle}>{titre}</button>)
})
return solo
use:
const data = await fetch(item + "?f=pjson")
.then(res => res.json())
const titre = (data.name).replaceAll('_', ' ').substring(1).toLowerCase()
solo.push(<button id={donneesCateg["name"]} className="btn" value={item} onClick={this.selectChangeSingle}>{titre}</button>)
return solo
also, be sure to declare the function as async at the top:
generateButtons = async css => { //...

Related

react ant design table with json - updating data without key problem

I would like to display some json data (coming from my backend and handled in a hook) in a similar way to this : https://ant.design/components/table/#components-table-demo-tree-data
I have a column with a checkbox and a column with an input (both must be editable by the user) and the final json state must be updated with the new datas.
Here you can find the structure of my json : https://pastebin.com/wA0GCs1K
Here you have a screen of the final result :
The code I used to fetch the data :
const [dataServiceInterface, setDataServiceInterface] = useState(null);
useEffect(() => {
CreateApiService.fetchDataServiceInterface(questionFiles, responseFiles).then((result) => {
if (result != null) {
setDataServiceInterface(result);
}
});
}, []);
Here you have the code I used to update the attributes (column constant for the second part) :
const onInputChange = (key, record, index) => (e) => {
e.preventDefault();
console.log(key);
console.log(index);
console.log(record);
console.log(e.target.value);
dataServiceInterface.itemsQ[index][key] = e.target.value;
};
// some other code here
{
title: "Json Name",
dataIndex: "name",
key: "name",
render: (text, record, index) => (
<Input
//defaultValue={text}
value={text}
onChange={onInputChange("name", record, index)}
/>
),
},
Problem is (I think) : As I dont have a key defined in my json datas (parents and children), when I try to update the children it dosent work. I can't change the structure of the json because it's a business constraint. I was thinking of copying my json data in another state, then add the keys ... and still didn't try this solution and don't know if it works. I will update this if it's the case.
Meanwhile, if someone had the same issue and has any idea/hint/suggestion, would appreciate very much. Thx.
I think the problem here is that the component is not rendering the data once you have it. This can be solved by using useState hook, you should lookup for the docs.
I would love to get a little bit more of the component that you are building. But the approach to this would be something like this:
const Component = () {
const [data, useData] = useState([]);
const onInputChange = (key, record, index) => (e) => {
e.preventDefault();
const data = // You get fetch the data here
useData(data);
};
return <>
{
data && data.itemsQ.map((item) => {
// here you display the information contained in each item
})
}
</>
}

Local storage being overwritten with new data

Hello guys I made a history page so the user can look at their past search history and so I used localstorage. On the history page, I am trying to render data that stays there and isn't changed when I go to search the api again. Instead I want it to keep adding data to the page. I was thinking the data would just keep being added to the new array in local storage but it overwrites the existing data with new data. Ive made an attempt to prevent this but I am stuck.
Here is my code of all of my pages
Search page
export default function SearchPage(props) {
// creating state to fetch the api
const [search, setSearch] = useState('')
// this is my function to monitor the words that are put into the input so these keywords that only matches specific data
// in the api and so one the data is fetched it renders the topic related to that keyword
const handleSearch = (event) => {
setSearch(event.target.value)
}
const handleSubmit = (event) => {
event.preventDefault();
// this is where I bring my useState variable to monitor the state of the key words in order to
// target specific data from the api
let url = `http://hn.algolia.com/api/v1/search_by_date?query=${search}`;
axios
.get(url)
.then((response) => {
const result = response.data.hits;
// this pushes the data fetched from the api to the results page using history
props.history?.push ({
pathname: '/results',
state: result,
});
})
// catching any errors to let me know if there is something going on in the .then function
.catch((error) => {
console.log(error)
console.log('Error while fetching data!')
})
}
return (
<div>
<div className="search-form-container">
{/* my form in where I search data by keywords */}
<form onSubmit={handleSubmit}>
<input type='text' placeholder="search" onChange={handleSearch} value={search}/>
<button type="submit">Search</button>
</form>
<hr/>
<Link to="/history">Your Search History</Link>
</div>
</div>
)
}
Results page
export default function ResultsPage(props) {
console.log(props)
// the variable declared below is where I am bringing in the data through history from the api.
const data = props.history.location.state;
let storedData = localStorage.setItem('data', JSON.stringify(data))
console.log(storedData)
// being that I am using history, I can map through the array of objects on the results page as shown below and then render it
const hitList = data.map((data, idx) => {
return (
<ul key={idx}>
<div>
<li> Author: {data.author}</li>
<li>Story Title: {data.story_title}</li>
<li>Comment: {data.comment_text}</li>
<li>Created on: {data.created_at}</li>
<li></li>
</div>
<hr/>
</ul>
)
})
return (
<div>
<Link to='/'><h1>Search</h1></Link>
<Link to='/history'><h1>Your History</h1></Link>
{/* In order for me to show my data I first had to map through the array of objects and then put the variable "hitlist" in the return */}
<h3>{hitList}</h3>
</div>
)
}
History page
export default function SearchHistoryPage(item) {
const storedData = JSON.parse(localStorage.getItem('data'));
storedData.push(item);
localStorage.setItem('data', JSON.stringify(storedData));
console.log(storedData);
const searchHistory = storedData.map((data, idx) => {
return (
<ul key={idx}>
<li> Author: {data.author}</li>
<li>Story Title: {data.story_title}</li>
<li>Comment: {data.comment_text}</li>
<li>Created on: {data.created_at}</li>
</ul>
)
})
return (
<div>
<h2>{searchHistory}</h2>
</div>
)
}
In your Results page, you are overwriting your localStorage 'data' key with only the results fetched from Search page.
What you can do is to fetch your "history" (localStorage 'data') before you push the new results in your Results page, and not in your History page as so:
In Results page:
const data = props.history.location.state;
// ADD THESE 2 LINEs
const historicData = JSON.parse(localStorage.getItem('data'));
historicData.push(data)
// Store "historicData" instead of "data"
// let storedData = localStorage.setItem('data', JSON.stringify(data))
let storedData = localStorage.setItem('data', JSON.stringify(historicData))
console.log(storedData)
In History page:
// Just use localStorage 'data'.
// Do not modify anything in History page since you are not taking any actions here.
const storedData = JSON.parse(localStorage.getItem('data'));
storedData.push(item);
// localStorage.setItem('data', JSON.stringify(storedData)); <-- REMOVE
// console.log(storedData); <-- REMOVE

Passing props to an axios delete request

The objective is to, by pressing a button, delete the object from the database.
To do that, I have to pass the ID of the object I want to delete from the database to the axios query. But I'm stuck trying to do it.
In my opinion the problem is I am not passing the ID to erase to the query, since the query seems right to me.
File: persons.js
import axios from 'axios'
const baseUrl = 'http://localhost:3001/persons'
const deleteContact = (id) =>{
const request = axios.delete('{$baseUrl}/${id}')
request.then(response =>response.data)
}
export default {
deleteContact: deleteContact,
}
The button that should call the function to delete:
File: person.js
import React from 'react'
const Person = ({ person, deleteContact }) => {
return (
<li>
{person.name} {person.number}
<button onClick={deleteContact(person.id)}>Delete {person.id} </button>
</li>
)
}
export default Person
So, by pressing the button I execute the deleteContact funtion and I pass to that function the person.id so it sends the id to delete.
Here is waht's wrong. I don't know how to make the function deleteContact.
I have tried this, but of course I am not sending any props. It's wrong and does nothing. I get the error TypeError: deleteContact is not a function.
const deleteContact = (id) => {
}
The deleteContact funtion I try to implement is on the file App.js
It is something obvious I am missing here. But I can't figure out what is.
Likely something basic, but I have been stuck here for a while, as silly this may seem to be.
File: App.js
import React, { useState, useEffect } from 'react'
import Person from './components/Person'
import Form from './components/Form'
import Filter from './components/Filter'
import FilterResults from './components/FilterResults'
import contactService from './services/persons'
//npm run server
const App = () => {
//Reminder: current state, function that updates it, initial state.
const [ newName, setNewName ] = useState('')
const [ newNumber, setNewNumber ] = useState('')
const [contacts, setContacts] = useState([])
//Filter
const [ filter, setFilter ] = useState('')
//contactService is importer from /services/persons.
//.getAll is like typing: axios.get('http://localhost:3001/persons')
//Effect hooks used to fetch data from the server. The data fetched is saved
//into the contacts state variable
useEffect(() => {
contactService
.getAll()
.then(response => {
setContacts(response.data)
console.log(contacts)
})
}, [])
/*
second parameter of useEffect is used to specify how often the effect
is run. If the second parameter is an empty array [],
then the effect is only run along with the first render of
the component. */
console.log('render', contacts.length, 'contacts')
//adding new persons
const addPerson = (event) => {
event.preventDefault()
/* complete the addPerson function for creating new persons */
const personObject = {
name: newName,
number: newNumber,
//The server will create the id
//id: persons.length + 1,
}
//Adding the data to the server
/*
using separate server comunication module from persons.js
"create" instead of previous code:
axios
.post('http://localhost:3001/persons', personObject)
replaced by:
contactService
.create(personObject)
*/
contactService
//Passing personObject to create
.create(personObject)
.then(response => {
console.log(response)
//After concat, the fiel is set to blank again ('').
//Updating state after creating, to display created contact.
setContacts(contacts.concat(personObject))
setNewName('')
setNewNumber('')
})
}
//Delete contacts
const deleteContact = (personObject) => {
}
const handlePersonChange = (event) => {
console.log(event.target.value)
setNewName(event.target.value)
}
const handleNumberChange = (event) => {
console.log(event.target.value)
setNewNumber(event.target.value)
}
const handleFilterChange = (event) => {
setFilter(event.target.value)
}
const personsToShow = filter === ''
? contacts
: contacts.filter(person =>
person.name.toLowerCase().includes(filter.toLowerCase()))
const row_names = () => personsToShow.map(person =>
<p key={person.name}>{person.name} {person.number} </p>
)
return (
<div>
<Filter value={filter} onChange={handleFilterChange} />
<Form
onSubmit={addPerson}
name={{value: newName, onChange: handlePersonChange}}
number={{value: newNumber, onChange: handleNumberChange}}
deleteContacts={() => deleteContact()}
/>
<h2>Numbers from database</h2>
{/* The contents of the database are stored on the variable contacts.
I map through the array. Person.js component used. */}
<ul>
{contacts.map(person =>
//Pass all the props from person to Person.js
<Person
key={person.id}
person={person}
/>
)}
</ul>
<h2>Filter results</h2>
<FilterResults persons={row_names()} />
</div>
)
}
export default App
The dabatbase is hardcoded json.
file db.json
"persons": [
{
"name": "ss",
"number": "ssssd",
"id": 17
},
{
"name": "ddd",
"number": "6tyhhth",
"id": 18
},
{
"name": "almejas",
"number": "1234",
"id": 19
},
{
"name": "pailo",
"number": "244",
"id": 20
}
]
}
OK. Once again. To handle API requests you have to build API server first. When you send ajax reqests they must go somewhere, and there must be a program listening requests and doing something what depends on request params and body. The repository you show contains only frontend logic and it is OK because server side logic and front-end can be kept separately and run indepentendly. In the same git account you may find some other reps like this one https://github.com/inci-august/fullstackopen/tree/d6680a40d03536e20ee9537cc64e1cb57dd6b74a/part3/phonebook-backend containing back-end implementation. So you build API server, accepting requests, doing something (create/delete posts, users, auth etc) and sending something back, and AFTER you find it working you can send API requests from frontend. Before front part is created you may use apps like Postman to test your API server.
UPDATE:
You mentioned the following link https://fullstackopen.com/en/part2/altering_data_in_server containing the same I have already said - the server side logic does not suppose to be implemented on this step.
In the next part of the course we will learn to implement our own
logic in the backend. We will then take a closer look at tools like
Postman that helps us to debug our server applications
As for you question - the "props" in requests to server can be sent by params in address string like:
axios.delete("api/person/2")
In the example above we say to server that we want the person with id=2 to be deleted. On the server side it will be catched with instruction like:
router.delete("api/person/:id", (req, res) => {
const id = req.params.id
// here you delete the person information and send response back
})
ID here will be handled by server as parameter and you will have an access to its value for further actions.
The exercise expected me to remove objects from the json database just using axios. Perhaps I did not express myself clarly enough, but I finally solved it.
I just wanted to pass the id to delete of each object to the axios request.
On the return sttement of App.js, I pass the person object as props to deleteContactOf
<ul>
{contacts.map(person =>
//Pass all the props from person to Person.js
//Here you pass the props
<Person
key={person.id}
person={person}
deleteContact={() => deleteContctOf(person)}
/>
)}
</ul>
This is how the function deleteContactOf looks like
//Delete contacts
const deleteContctOf = (person) => {
console.log(person)
console.log('delete contact ' + person.id + ' ????')
if (window.confirm("Do you really want to delete this person")) {
contactService
.remove(person.id)
} else {
return
}
}
deleteContactOf passes the id (person.id) to the axios request on file perons.js
Now the id is passed. That's were I was failing.
const remove = (id) => {
const req = axios.delete(`${baseUrl}/${id}`)
return req.then((res) => res.data)
}
Now by clicking the delete button, the contacts that belong to that id are deleted
<button onClick={deleteContact}>Delete</button>
Thanks for your time effort. Of course this is implemented with APIs in real life. This was just an specific exercise I had to solve.
Thanks

How to load images from firebase to carousel react?

I'm trying to get some images from firebase storage loaded into a react-responsive-carousel. The problem is that only 1 or 2 or even none of the images are loaded (there are at least 10 images). This is what I'm trying:
const [imagenes, setImagenes] = useState([]);
useEffect(() => {
const fetchedImagenes = [];
firebase.storage().ref().child('PR/Gallery').listAll().then(function (result) {
result.items.forEach(function (itemRef) {
itemRef.getDownloadURL().then(function (link) {
const fetchedImagen = link
fetchedImagenes.push(fetchedImagen)
})
});
setImagenes(fetchedImagenes)
})
}, [])
And this is the carousel code:
<Carousel dynamicHeight={true}>
{imagenes.map(imagen => {
return <div>
<img src={imagen} />
</div>
})}
</Carousel>
My guess is that the imagenes array is not filled when I show the images, so is there any way that I can wait for the imagenes array to be filled and then show the images?
You're now calling setImagenes(fetchedImagenes) before any of the fetchedImagenes.push(fetchedImagen) has been called, so you're setting an empty array.
The quick fix is to set the array to the state whenever a download URL is determined by moving it into the then callback:
firebase.storage().ref().child('PR/Gallery').listAll().then(function (result) {
result.items.forEach((itemRef) => {
itemRef.getDownloadURL().then((link) => {
const fetchedImagen = link
fetchedImagenes.push(fetchedImagen)
setImagenes(fetchedImagenes)
})
});
})
This will work, but may result in some flicker as you're updating the UI whenever any download URL is determined.
If you want to only update the state/UI once, you're looking for Promise.all and it'll be something like:
firebase.storage().ref().child('PR/Gallery').listAll().then(function (result) {
const promises = result.items.map((itemRef) => itemRef.getDownloadURL());
Promise.all(promises).then((urls) =>
setImagenes(urls)
});
})

How to map async key to call API

I have 2 classes, App.js and Categories.js. the App.js called an API and managed to get the name of the categories and the ID of the category into JSON, the data is then pushed like so and gets transfered into a prop of class Category
Object.keys(data.categories).forEach((category,index) => {
categories.push(data.categories[index].name);
categoryIDs.push(data.categories[index].id)
})
<Categories
categoryName = { this.state.categoryName }
categoryID = { this.state.categoryID }
/>
Then in my Categories class, the data is returning a pair through a map button, and i used key to associate with the array values like a pair but I can't pass the values to help get the values needed to dynamically access the API
class Categories extends React.Component {
getResult= async () => {
const api_call = await fetch(`
http://callthisapi.com/v1/categories/items?format=json&category=1334134&apiKey=${API_KEY}`)
// Convert response to json
const data = await api_call.json();
console.log(data);
}
render() {
return (
<div>
<br />
{
this.props.categoryName.map
((name,i) => <button key={i} onClick={this.getResult}> {name} {this.props.categoryID[i]} </button>)
}
</div>
as you can see the 1334134 is a hard coded value right now but the number is currently associated in {this.props.categoryID[i]}. How do i allow the number to change dynamically? I tried to pass in the value through a parameter but everything broke afterwards.
The value that I want is in {this.props.categoryID[i]} and I can't extract it to the function is the tl;dr
Change your onClick to be onClick={() => this.getResult(this.props.categoryID[i])}.
Then getResult can accept a parameter: getResult = async (id) => { ... }
API call: http://callthisapi.com/v1/categories/items?format=json&category=${id}
See React documentation for passing functions: https://reactjs.org/docs/faq-functions.html

Resources