I need data from 2 different APIs, both support pagination.
How can I display the joined data in a table in a performant way?
I need to join on their id, but one API returns less data than the other, so I cannot simply match 1 by 1. I need to implement a filter.
Is the only way to brute force map data source A to B?
If the data comes from two different APIs and you are making to separate requests you have a number of options. My personal preference is to have state in your controller in which you map each response by id and then you can select the additional data by id:
import React, { useState, useEffect } from 'react';
import { keyBy } from 'lodash';
function TableComponent(props) {
// Destructure your props...I'm assuming you pass some id into fetch data
const { id } = props;
// State
const [tableData, setTableData] = useState([]);
// Load data when id changes
useEffect(() => {
fetchData()
}, [id]);
async function fetchData() {
// Get your data from each source
const apiData_A = await fetchDataFromAPI_A(id);
const apiData_B = await fetchDataFromAPI_B(id);
// Key each data set by result ids
const resultsMappedById_A = keyBy(apiData_A, 'id');
const resultsMappedById_B = keyBy(apiData_B, 'id');
// Combine data into a single set
// this assumes your getting same results from each api
const combinedDataSet = Object.keys(resultsMappedById_A)
.reduce((acc, key) => {
// Destructure results together, merging objects
acc.push({
...resultsMappedById_A[key],
...resultsMappedById_B[key]
});
return acc;
}, []);
setTableData(combinedDataSet);
}
async function fetchDataFromAPI_A(id) {
// Fetch your data and return results
}
async function fetchDataFromAPI_A(id) {
// Fetch your data and return results
}
function renderTableRow(data) {
return (
<tr>
<td>{data.id}</td>
<td>{data.apiAProp}</td>
<td>{data.apiBProp}</td>
</tr>
);
}
return (
<table>
{ tableDataSet.map(renderTableRow) }
</table>
);
}
Note, there are probably more efficient ways to do this depending on how you're fetching data and what the responses hold, but the concept provided here should do the trick assuming my assumptions are correct based on the information you have provided.
Related
Hello I made a createAsyncThunk function which is manipulating the actual state (it removes value with specific index). It executes but it doesn't update the firebase database and I'm not getting the value of categories array in the extraReducers. I don't know why because when I use console.log the categories array has got values. I'm using typescript and redux toolkit.
export const removeCategory = createAsyncThunk(
"categories/removeCategory",
async (index: number, thunkAPI) => {
const state = thunkAPI.getState() as RootState;
const categories = [...state.categories];
categories.splice(index, 1);
console.log(categories);
const updates = { categories: [] as string[] };
updates["categories"] = [...categories];
update(ref(database), categories);
return categories;
}
);
In this part of the code, are you sure you don't want to send the updates object instead of the categories?
const updates = { categories: [] as string[] };
updates["categories"] = [...categories];
update(ref(database), updates);
return categories;
When I execute an API request, I get a lot of objects that are not connected by a common array. I'm trying to combine them into one state, but only one value is stored. How can I store all the received objects in the general array of my state? Everything would be fine, but I need the array to be stored in the state, in which all objects from the API will lie, in order to transfer this array to Redax
const [planets, setPlanets] = useState([]);
useEffect(() => {
(async () => {
const users = await axios(world);
const worldData = users.data;
setPlanets(worldData);
})();
}, [nextTeam, world]);
and i take get data
I am trying to transfer all these objects to state planets, but objects are transferred there individually. Not creating an array.
This is a screenshot of console.log(planets)
You can use Object.keys
let planets = {
tatooine:{
population: "1,3M", type: "desert"
},
coruscant:{
population: "130B", type: "city"
},
}
let array = []
Object.keys(planets).forEach(planet => array.push({
name: planet,
...planets[planet]
}))
console.log(array)
I have an async function that gets data from my Supabase database, that when called, returns a promise with the correct data that I have queried, but when I try to call this function in a React component, I don't know how to extract the data from the Promise and just get the string that I queried.
I understand that you can not get the result of a promise in the same scope as you call it, but I'm not sure how I would get around this.
My code:
export async function getUserValue(uuid, value) {
const { data, error } = await supabase
.from('users')
.select('username').eq("id", "8f1693d3-c6d9-434c-9eb7-90882ea6ef28"); // hard coded values for testing purposes
return data;
}
Where I call it:
...
async function Sidebar(props) {
console.log(getUserValue("", ""))
return (
<div className={"sidebar"}>
<div className="sidebar-main">
<img className={"sidebar-main-picture"} src={profile_picture} alt="pfp"/>
<p className={"sidebar-main-name"}>Test</p>
...
Result
The way to store data in React components is to define and set state.
The correct place to handle side-effects like async response data is in an effect hook
import { useEffect, useState } from "react";
function Sidebar(props) {
const [ user, setUser ] = useState(null); // initial value
useEffect(() => {
getUserValue("", "")
.then(users => {
setUser(users[0]) // your response is an array, extract the first value
})
.catch(console.error)
}, []); // empty array means run this once on mount
return user && ( // only display if `user` is set
<p>Hello, { user.username }</p> {/* just an example */}
);
}
I feel like this has definitely been asked and answered before but I couldn't find an applicable duplicate. Happy to remove this or mark it Community Wiki if somebody can link an existing post.
I'm trying to append array which is react state:
const [ products, setProducts ] = useState([])
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
const copy = JSON.parse(JSON.stringify(products))
copy[category.id] = data
setProducts(copy)
})
})
},[])
service.getCategory() fetches data over HTTP returning array. products is nested array, or at least it's suppose to be. config.category is defined as:
categories: [
{
name: 'product1',
id: 0
},
{
name: 'product2',
id: 1
},
{
name: 'product3',
id: 2
}]
}
Eventually products should be appended 3 times and it should contain 3 arrays containing products from these categories. Instead products array ends up including only data from last HTTP fetch, meaning the final array looks something like this
products = [null, null, [{},{},{},..{}]].
I hope someone knows what's going on? Been tinkering with this for a while now.
The problem is that your fulfillment handlers close over a stale copy of products (the empty array that's part of the initial state). In a useEffect (or useCallback or useMemo, etc.) hook, you can't use any state items that aren't part of the dependency array that you provide to the hook. In your case, you just want to get the data on mount, so an empty dependency array is correct. That means you can't use any state items in the callback.
What you can do instead is use the callback form of the state setter:
const [ products, setProducts ] = useState([]);
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
setProducts(products => { // Use the callback form
const copy = products.slice(); // Shallow copy of array
copy[category.id] = data; // Set this data
return copy; // Return the shallow copy
});
});
});
}, []);
Or more concisely (but harder to debug!) without the explanatory comments:
const [ products, setProducts ] = useState([]);
useEffect(() => {
config.categories.forEach(category => {
service.getCategory(category.name).then(data => {
setProducts(products => Object.assign([], products, {[category.id]: data}));
});
});
}, []);
Those both use the same logic as your original code, but update the array correctly. (They also only make a shallow copy of the array. There's no need for a deep copy, we're not modifying any of the objects, just the array itself.)
But, that does a state update each time getCategory completes — so, three times in your example of three categories. If it happens that the request for id 2 completes before the request for id 1 or 0, your array will look like this after the first state update:
[undefined, undefined, {/*data for id = 2*/}]
You'll need to be sure that you handle those undefined entries when rendering your component.
I have an object that looks something like this
messageData = { items: [{name:'1st msg' draft:{contact: endpoint}},
{name:'1st msg' draft:{contact: endpoint}},
{name:'1st msg' draft:{contact: endpoint}]}
I need to map over the items and do an api call that expands the data from the api endpoint and return them in a table row
renderMessageTableRow() {
let dataRef = this.state.messageData.items,
return(_.map(dataRef, (message) => {
// Get vars from message data including contact method endpoint
let id = message['#id'],
status = message.status,
draftData = message.draft.recipientAddress
return (
<tr>
<td>{id}</td>
<td>{status}</td>
</tr>
)
})
)
}
so far my function looks like this but draftData is an endpoint I need to expand and populate my table row with, I'm using React and Redux. I've been looking a libraries like async and co but not sure how I would plug this in my map function. I just need to call the endpoint make some variables from that data and populate the rest of my table with it. Any help would be much appreciated
There's a number of ways you can get data and store it for rendering (redux for example). Here's a simple example that just uses the component's state and makes the ajax calls on componentDidMount. Most likely you'd do this kind of thing out of the component, but this should give you a sense of some of the flow. It uses isomorphic fetch which you can get on NPM.
import React from 'react';
require('es6-promise').polyfill();
require('isomorphic-fetch');
const messageData = {
items: [
{ name: '1st msg', draft: { contact: 'http://something.something/api' } },
{ name: '1st msg', draft: { contact: 'http://something.something/api' } },
{ name: '1st msg', draft: { contact: 'http://something.something/api' } },
],
};
class MyComponent extends React.Component {
constructor() {
super();
this.state = { rows: undefined };
}
componentDidMount() {
// make an array of promises that are your api calls
const promises = messageData.items.map((item, i) => fetch(item.draft.contact).then(data => {
messageData.items[i].draft.data = data;
return data;
}));
// resolve all the api calls in parallel and populate the messageData object as they resolve
Promise.all(promises).then(responses => {
// messageData.items are populated now, but if you want to do anything additional, the data from the ajax calls is all here
console.log(responses);
// set the populated items in state
this.setState({ rows: messageData.items });
});
}
render() {
// conditionally populate the rows constant
const rows = (this.state.rows) ? this.state.rows.map(row => (
<tr>
<td>{row.draft.data.somePropFromApi}</td>
</tr>
)) : null;
return (
<table>
{rows}
</table>
);
}
}
export default MyComponent;