How to map async key to call API - reactjs

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

Related

SWR Keep getting the same data through the loop

I have home component and i passing props into Product card component
{products.map((product: ProductListStoreType) => (
<ProductCard
key={product.slug}
product={product}
category={product.category}
/>
))}
</div>
Category is just string _id from mongo db
and inside the product card
export default function ProductCard({ product, category }: ProductCardProps) {
const getCategory = async () => {
const { data } = await axios.get(`/api/category/${category}`);
return data.name;
};
const { data } = useSWR('/api/category', getCategory);
console.log(data);
The problem is SWR returns the same data for every loop. the id category is unique because it is from the _id mongo DB. but SWR keeps returning the same data which is from the category _id from the first loop
awlays return the first data for every map
The first parameter of useSWR is a unique key identifying the request. Since the key you are passing does not change (it's always '/api/category'), the data is not refreshed.
The key will be as the first argument to your fetcher, see the docs on arguments.
With these two things in mind, you could do something like this:
const getCategory = async (path) => {
const { data } = await axios.get(path);
return data.name;
};
export default function ProductCard({ product, category }: ProductCardProps) {
const { data } = useSWR(`/api/category/${category}`, getCategory);

React fetch JSON data inside loop

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 => { //...

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

Object into an array then return a component - React JS Application

Evening fellow developers,
I've rewrote a React JS application, but I've just hit a brick wall.
I have a form which adds some data to a database, specifically Firebase googles very own cloud based database solution. However, now I'm trying to fetch the data and render the components below but I'm sure how to do this.
What I receive from Firebase as a response :
Response From Firebase
Currently I'm just logging the response to show I do receive a response, I have set the response to an empty state. On render it gets set to the response from the server.
I want to be able to now convert the objects into an array which can then return an array of responses. The array will then be looped through and transformed into components which will get rendered below as I specified above.
Can someone assist me with this task as I'm very unsure how to accomplish this task. I will appreciate any responses to my question.
Github Link: https://github.com/AlexMachin1997/React-JS-Contact-Book
What i have so far:
componentDidMount () {
axios.get("/contact.json")
.then(response => {
this.setState({
contactsArray: response.data.person
})
console.log(this.state.contactsArray)
})
//Any Errors The Error State Is Set To True
.catch (error => {
console.log(error)
})
}
To convert the response object into an array you can use Object.keys or Object.values
Object.values(firebaseResponse);
or
Object.keys(firebaseResponse).map((x) => {
// additional transformation
return x;
});
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_objects/Object/values
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys
You can use Object.values or Object.keys to get an array from your object. But Object.keys is more suitable if you don't have unique id's in your persons object since you will need a key to iterate a JSX prop or your component. If response is in your root state:
render() {
const obj = this.state;
return (
Object.keys( obj ).map( objKey =>
(
<div key={objKey}>
<p>Name: {obj[objKey].persons.name}</p>
<p>Email: {obj[objKey].persons.email}</p>
<p>Phone: {obj[objKey].persons.phone}</p>
</div>
)
)
);
}
or pass it to another component:
render() {
const obj = this.state;
return (
Object.keys(obj).map(objKey =>
<Person obj={obj} objKey={objKey} />
)
);
}
With Object.values this will be slightly cleaner but you should use something unique for your key. I've chosen email property since emails are unique.
render() {
const obj = this.state;
return (
Object.values(obj).map(value =>
(
<div key={value.persons.email}>
<p>Name: {value.persons.name}</p>
<p>Email: {value.persons.email}</p>
<p>Phone: {value.persons.phone}</p>
</div>
)
)
);
}
or again with a component:
render() {
const obj = this.state;
return (
Object.values(obj).map(value =>
<Person value={value} />
)
);
}

HOC/Render-Call Back or Library function?

I'm working on a project where a prospect needs to be sent an email about a property they are interested in. There is a top level component that fetches the property information and prospect's contact info from the database and passes to its children. There are two components that share the same process of formatting the information, and then call an email function that sends off an email. A sample of one component looks like this:
import sendEmail from 'actions/sendEmail'
class PropertyDetail extends React.Componet {
state = {
unit: undefined,
prospect: undefined,
};
componentDidMount = () => {
this.setState({
unit: this.props.unit,
prospect: this.props.prospect,
});
};
sendEmail = ({ id, address, prospect }) => {
// quite a bit more gets formatted and packaged up into this payload
const payload = {
id,
address,
prospectEmail: prospect.email,
};
emailFunction(payload);
};
handleEmail = () => {
sendEmail(this.state);
};
render() {
return (
<div>
<h1>{this.state.unit.address}</h1>
<p>Send prospect an email about this property</p>
<button onClick={this.handleEmail}>Send Email</button>
</div>
);
}
}
and the other component looks like this
class UpdateShowing extends React.Component {
state = {
unit: undefined,
prospect: undefined,
showingTime: undefined,
};
componentDidMount = () => {
this.setState({
unit: this.props.unit,
propsect: this.props.prospect,
showingTime: this.props.showingTime,
});
};
sendEmail = ({ id, address, prospectEmail }) => {
// quite a bit more gets formatted and packaged up into this payload
const payload = {
id,
address,
prospectEmail,
};
emailFunction(payload);
};
handleUpdate = newTime => {
// get the new date for the showing ...
this.setState({
showingTime: newTime,
});
// call a function to update the new showing in the DB
updateShowingInDB(newTime);
sendEmail(this.state);
};
render() {
return (
<div>
<p>Modify the showing time</p>
<DatePickerComponent />
<button onClick={this.handleUpdate}>Update Showing</button>
</div>
);
}
}
So I see some shared functionality that I'd love to not have to repeat in each component. I'm still learning (working my first job), and why not use this as an opportunity to grow my skills? So I want to get better at the HOC/Render props pattern, but I'm not sure if this is the place to use one.
Should I create a component with a render prop (I'd rather use this pattern instead of a HOC)? I'm not even sure what that would look like, I've read the blogs and watched the talks, ala
<MouseMove render={(x, y) => <SomeComponent x={x} y={y} />} />
But would this pattern be applicable to my case, or would I be better off defining some lib function that handles formatting that payload for the email and then importing that function into the various components that need it?
Thanks!
I think a provider or a component using render props with branching is a better fit for you here
see this doc: https://lucasmreis.github.io/blog/simple-react-patterns/#render-props

Resources