useEffect Fetch showing 0 array in react - reactjs

I am creating a React app that uses Fetch to pull a API using SQLite, however for some reason it shows in console length: 3, and Array[0] only. I cannot pull from data just id 1 for example.
import React, { useState, useEffect } from "react";
export default () => {
const [brands, setBrands] = useState(null);
useEffect(() => {
fetch("/api/brands")
.then((response) => response.json())
.then((data) => console.log(data))
.then((data) => {
setBrands(data);
});
}, []);
return (
<>
{brands ? (
<>
<h1>Brands</h1>
<ul>
{brands.map((brand) => (
<li key={brand.id}>{brand.name}</li>
))}
</ul>
</>
) : (
<div>Loading...</div>
)}
</>
);
};
How would I be able to pull from this id 1 for example? at the moment only all the brands show when I remove the console log and show as I pasted in the screen shot above in console.

If I understand your question then you want to display only one item from an array of items fetched via an API.
Its not clear where or how you determine which item you want. Is it a component prop?
My first suggestion is simply implement an API endpoint that returns only one item based on a parameter eg.
fetch(`/api/brands/${some_id_for_the_record_i_want}`)
If you can't modify the API then you can use filter/find to limit the items you want BEFORE setting state eg. - this example uses find which returns a single match or undefined.
useEffect(() => {
fetch("/api/brands")
.then((response) => response.json())
.then((data) => {
setBrands(data.find(f => f.id === 1));
});
}, []);
Worst case just do filter/find in the JSX eg. - this example uses filter which returns an array of matches or an empty array.
return (
<>
{(() => {
if(brands) {
const filteredBrands = brands.filter(f => f.name === 'somename');
return (
<>
<h1>Brands</h1>
<ul>
{filteredBrands.map((brand) => (
<li key={brand.id}>{brand.name}</li>
))}
</ul>
</>
)
// Want a single match?
// const singleBrand = brands.find(f => f.id=== 1);
//return (
// <>
// <h1>Brands</h1>
// <div>{singleBrand?.name}<div>
// </>
//)
} else {
return <div>Loading...</div>
}
})()}
</>
);

From what I understand, you want to show data for a single id (i.e. brand) instead of for all brands. I would do it like this.
import React, { useState, useEffect } from "react";
export default () => {
const [allBrands, setAllBrands] = useState(null);
const [specificBrand, setSpecificBrand] = useState(null);
useEffect(() => {
fetch("/api/brands")
.then((response) => response.json())
.then((data) => {
setAllBrands(data);
});
}, []);
useEffect(() => {
if(allBrands.length){
setSpecificBrand(allBrands.find(brand => brand .id === 1);
}
}, [allBrands]);
return (
<>
{specificBrand? (
<>
<h1>Brands</h1>
<ul>
<li key={specificBrand.id}>{specificBrand.name}</li>
</ul>
</>
) : (
<div>Loading...</div>
)}
</>
);
};
The API endpoint suggestion also seems like a good idea.

Related

What's the standard way of calling nested API's with react/typescript?

I'm trying to make a small app that reads an endpoint to get a list of items. The app will then loop through and pull the details for the items. I did get a version to work but it breaks the rules of hooks. What is the standard / correct way?
This code should trigger the useEffect on load to set the Globals. Then trigger the useEffect to setLocalData and then trigger the useEffect to add the localdata to the localDataList
When I run this code I get the list of the 2 global lines. That works great. The details part is where it gets wonky. First, I can't figure out how to get rid of the leading null in the local. Secondly, if I refresh over and over, I get only 1 item sometimes.
import React from 'react';
import { useState, useEffect } from 'react';
import { Buffer } from 'buffer';
import algosdk from 'algosdk';
export declare module DataStore {
export interface Events {
"global-state": KeyValue[];
}
export interface EventDetails {
"key-value": KeyValue[];
}
export interface Value {
bytes: string;
type: number;
uint: number;
}
export interface KeyValue {
key: string;
value: Value;
}
}
function App() {
const [globals, setGlobals] = useState<DataStore.Events[]>([]);
const [localDataList, setLocalDataList ] = useState<any>([])
const [localData, setLocal] = useState<DataStore.EventDetails[]>();
useEffect(() => {
fetch("https://node.algoexplorerapi.io/v2/applications/1022971265")
.then((response) => response.json())
.then((data) => setGlobals(data.params["global-state"].filter((item: any) => item.key !== "VGl0bGU=" )));
}, []);
useEffect(() => {
globals.forEach(function (item: any) {
//console.log(globals);
const pretty_address = algosdk.encodeAddress(new Buffer(item.value.bytes, 'base64'));
//console.log(pretty_address)
fetch("https://node.algoexplorerapi.io/v2/accounts/" + pretty_address + "/applications/1022971265")
.then((response) => response.json())
.then((data) => setLocal(data["app-local-state"]["key-value"] )); //
})
}, [globals]);
useEffect(() => {
console.log(localData)
setLocalDataList([...localDataList, localData ])
}, [localData]);
return (
<div>
Globals:
<ul>
{globals.map((item: any) => (
<li key={item.key}>{
JSON.stringify(item)
}</li>
))}
</ul>
<br/>
Local list:
<ul>
{localDataList?.map((item) => ( <li> {JSON.stringify(item)} </li> )) }
</ul>
</div>
);
}
export default App;
Thanks for the help.
In your situation I would create a free function to do all the work, and return it once completed.
async function fetchLocalDataList() {
const res = await fetch("https://node.algoexplorerapi.io/v2/applications/1022971265")
const data = await res.json()
const globals = data.params["global-state"].filter((item: any) => item.key !== "VGl0bGU=")
const localDataList = []
for (const item of globals) {
const pretty_address = algosdk.encodeAddress(new Buffer(item.value.bytes, 'base64'));
const res = await fetch("https://node.algoexplorerapi.io/v2/accounts/" + pretty_address + "/applications/1022971265")
const data = await res.json()
localDataList.push(data)
}
return {globals, localDataList}
}
Then you component can be something like
function App() {
const [globals, setGlobals] = useState<DataStore.Events[]>([]);
const [localDataList, setLocalDataList ] = useState<any>([])
useEffect(() => {
fetchLocalDataList().then(({globals, localDataList}) => {
setGlobals(globals)
setLocalDataList(localDataList)
})
}, []);
return (
<div>
Globals:
<ul>
{globals.map((item: any) => (
<li key={item.key}>{
JSON.stringify(item)
}</li>
))}
</ul>
<br/>
Local list:
<ul>
{localDataList?.map((item) => ( <li> {JSON.stringify(item)} </li> )) }
</ul>
</div>
);
}
If you want to fetch from inside components like this I would however sugges you use something like react-query or SWR. It will deal with batching, concurrent calls, caching etc for you and make life easier.
Also, just to cover all the bases. Error checking here is omitted, but should be included in any code you actually planning to use.
to remove null, replace
{localDataList?.map((item) => ( <li> {JSON.stringify(item)} </li> )) }
with
{localDataList && localDataList?.map((item) => ( <li> {JSON.stringify(item)} </li> )) }
then there seems being a problem with global.foreach, you will repeteadly overwrite the localldata content since your are not adding items, but replacing them with this line :
.then((data) => setLocal(data["app-local-state"]["key-value"] )); //
prefer :
.then((data) => setLocal(newdata => [...newdata, data["app-local-state"]["key-value"] ]));
finally, useState<DataStore.EventDetails[]>(); is not initialized

Trying to get data from api and map to another component in React

I'm trying to map an array of movies which I get from an API.
The data is fetched successfully but when I try to map the values and display, it becomes undefined and does not show anything.
I'm new to React so any help and advice would be helpful.
const [items, setItems] = useState([]);
const getMovieData = () => {
axios
.get(api_url)
.then((response) => {
const allMovies = response.data;
console.log(allMovies);
setItems(allMovies);
})
.catch((error) => console.error(`Error: ${error}`));
};
useEffect(() => {
getMovieData();
}, []);
return (
<div>
{items.map((item) => {
<p>{item.title}</p>;
})}
</div>
);
The data is stored like this:
0: {
adult: false,
backdrop_path: '/9eAn20y26wtB3aet7w9lHjuSgZ3.jpg',
id: 507086,
title: 'Jurassic World Dominion',
original_language: 'en',
...
}
You're not returning anything from your map
{
items.map((item) => {
// Add a return
return <p>{item.title}</p>
})
}
First, your items value is an empty array[] as you have initialized with setState([]) and your useEffect() runs only after your component is rendered which means even before you could do your data fetching, your HTML is being displayed inside which you are trying to get {item.title} where your items is an empty array currently and hence undefined. You will face this issue often as you learn along. So if you want to populate paragraph tag with item.title you should fast check if your items is an empty array or not and only after that you can do the mapping as follow and also you need to return the element from the map callback. If it takes some time to fetch the data, you can choose to display a loading indicator as well.
const [items, setItems] = useState([]);
const getMovieData = () => {
axios.get(api_url)
.then((response) => {
const allMovies = response.data;
console.log(allMovies);
setItems(allMovies);
}).catch(error => console.error(`Error: ${error}`));
};
useEffect(() => {
getMovieData();
}, []);
return ( < div > {
items.length !== 0 ? items.map((item) => {
return <p > {
item.title
} < /p>
}) : < LoadingComponent / >
}
<
/div>
);
Good catch by Ryan Zeelie, I did not see it.
Another thing, since you're using promises and waiting for data to retrieve, a good practice is to check if data is present before mapping.
Something like :
return (
<div>
{ (items.length === 0) ? <p>Loading...</p> : items.map( (item)=>{
<p>{item.title}</p>
})}
</div>
);
Basically, if the array is empty (data is not retrieved or data is empty), display a loading instead of mapping the empty array.

Undefined when selecting div with information

Having an issue with a piece of my code. I fetch from flask server, and display with div in React. I want to select the div and have that information pass to a new object array to return back to flask, but I keep getting undefined.
Code snippet:
function PLCPage() {
const [myjunk, setMyjunk] = useState([]);
const [devList, setDevList] = useState ([]);
const Scan = () => {
fetch('/api/home').then(response => {
if(response.status === 200){
return response.json()
}
})
.then(data => setMyjunk(data))
.then(error => console.log(error))
}
const Clear = () => {
setMyjunk({})
}
Creating the divs:
{Object.keys(myjunk).map((key) =>{
return (
<div className='plc-container' key={key} onClick={ReadStuff}>
<h1>ID:{myjunk[key]['name']}</h1>
<h1>IP:{myjunk[key]['IP']}</h1>
</div>
)
Clicking on the div, just to return a console log returns undefined.
const ReadStuff = () => {
console.log(this.IP)
}
I eventually want to return the data I have in the 2 h1 tags to a new object (devList) but I can't even get it to console log. Sorry if this is basic but I've been stumped at this for a week. Thanks
I've tried this.IP, myjunk.IP, this,myjunk.IP. myjunk['IP']. Nothing returns. And when I do myjunk.IP I get "cant read from undefined"
One way to do this is to create a separate component:
const JunkButton = ({junk}) => (
<div className='plc-container' key={key} onClick={() => ReadStuff(junk)}>
<h1>ID:{junk['name']}</h1>
<h1>IP:{junk['IP']}</h1>
</div>
)
Now your map() looks like:
{Object.keys(myjunk).map((key) =>{ <JunkButton junk={ myjunk[key] }/> }
And ReadStuff becomes:
const ReadStuff = (junk) => { console.log(junk) }
Notice how in React we explicitly pass things around as props or function parameters.
first you need to pass myjuck to function and then console it like this:
{Object.keys(myjunk).map((key) =>{
return (
// sending myjuck to function whatever that is
<div className='plc-container' key={key} onClick={() => ReadStuff(myjunk)}>
<h1>ID:{myjunk[key]['name']}</h1>
<h1>IP:{myjunk[key]['IP']}</h1>
</div>
)
ReadStuff function
const ReadStuff = (myjunk) => { console.log(tmyjunk) }

Render an Array returns the correct amount but not displaying information in it

This is my data:
This is how I get my data and display a Card:
// Mainpage Links and Categories //
const MainpageLinks = () => {
const [mainpageDataCategories, setDataCategories] = useState([]);
React.useEffect( () => {
const getMainpageData = async () => {
let responseMainpage = await axios.get("ajax/api/mainpage_links")
const responseMainpageData = responseMainpage.data;
// Get the unique categories
const arrMainpageData = responseMainpageData.map(m => m.category);
const setMainpageData = new Set(arrMainpageData);
const uniqueCategories = Array.from(setMainpageData);
setDataCategories(uniqueCategories)
console.log(uniqueCategories);
}
getMainpageData();}, []);
return (
<>
{mainpageDataCategories.map(({ mainpageDataCategories }) => (
<Cards.Item
overline={mainpageDataCategories}
headline={mainpageDataCategories}
thumbIcon={communication___call}
subline={mainpageDataCategories}
thumbTitle="Test"
centeredLayout
actions={
<IconLink
icon={communication___call}
href="#"
look={IconLink.Looks.RIGHT}
onClick={() => console.log("IconLink clicked!")}
>
Mehr erfahren
</IconLink>
}
/>
))}
</>
);
}
The result shows the correct amount of <Card> items, but the data is not displayed. The same piece of code works if my data has values like [{"team": "1", "name": "tom"}].
How can I correctly display the values in my array in my <Card> item?
The issue here how you make your map mainpageDataCategories.map(({ mainpageDataCategories }) => says map this array to another array. But in the function you tell it to use the field { mainpageDataCategories }from the object in the array. This does not even exist and it uses the same name as the state which will confuse it even more.
Try to do this instead:
mainpageDataCategories.map(( category ) => (
<Cards.Item
overline={category}
headline={category}
thumbIcon={communication___call}
subline={category}
thumbTitle="Test"
centeredLayout
actions={
<IconLink
icon={communication___call}
href="#"
look={IconLink.Looks.RIGHT}
onClick={() => console.log("IconLink clicked!")}
>
Mehr erfahren
</IconLink>
}
/>
))
You could considering moving over to typescript or get a linter to help you catch this problems while coding.
{mainpageDataCategories.map((item) => (
<Cards.Item
overline={item}
headline={item}
thumbIcon={communication___call}
subline={item}
thumbTitle="Test"
centeredLayout
actions={
<IconLink
icon={communication___call}
href="#"
look={IconLink.Looks.RIGHT}
onClick={() => console.log("IconLink clicked!")}
>
Mehr erfahren
</IconLink>
}
/>
))}
This syntax seems incorrect, try this instead.
If still not working, try to log what item (in your case mainpageDataCategories), and be sure that Cards.Item component render the text in that way.

How can you make a createRef/useRef not target the last value. But rather go to where its suppose to

Can't manage to make useRef/createRef to get any other div's other then what was added last. How can i make it so when the button is clicked the ref to the div changes.
I've tried with both useRef and createRef. Since I want to make a new instance of ref, i've looked more into createRef rather then useRef.
I've also played around useEffect. But my solution didn't help me with my biggest problem
I have made a small project containing 3 components to help you understand what I'm trying to explain.
I also have a database containing mock data -> in my real project this isn't the problem. It's an array containing objects.
[{'id':'1', 'name':'first'},...]
Main:
const MainComponent = () => {
const dataRef = React.createRef(null)
React.useEffect (() => {
if(dataRef && dataRef.current){
dataRef.current.scrollIntoView({ behavior:'smooth', block:'start' })
}
},[dataRef])
const _onClick = (e) => {
dataRef.current.focus();
}
return(
<>
{data && data.map((entry, index) =>{
return <ButtonList
key={index}
entry={entry}
onClick={_onClick}
/>
})}
{data && data.map((entry, index) =>{
return <ListingAllData
key={index}
dataRef={dataRef}
entry={entry}
index={index}/>
})}
</>
)
}
Button Component
const ButtonList = ({ entry, onClick }) => {
return <button onClick={onClick}>{entry.name}</button>
}
Listing data component
const ListingAllData = (props) => {
const {entry, dataRef } = props;
return (
<div ref={dataRef}>
<p>{entry.id}</p>
<p>{entry.name}</p>
</div>
);
}
I've console logged the data.current, it only fetches the last element. I hoped it would fetch the one for the button I clicked on.
I think the main idea here is to create dynamic refs for each element (array of refs), that's why only the last one is selected when app renders out.
const MainComponent = () => {
const dataRefs = [];
data.forEach(_ => {
dataRefs.push(React.createRef(null));
});
const _onClick = (e, index) => {
dataRefs[index].current.focus();
dataRefs[index].current.scrollIntoView({
behavior: "smooth",
block: "start"
});
};
return (
<>
{data &&
data.map((entry, index) => {
return (
<ButtonList
key={index}
entry={entry}
onClick={e => _onClick(e, index)}
/>
);
})}
{data &&
data.map((entry, index) => {
return (
<>
<ListingAllData
key={index}
dataRef={dataRefs[index]}
entry={entry}
index={index}
/>
</>
);
})}
</>
);
};
Created working example in code sandbox.
https://codesandbox.io/s/dynamic-refs-so25v
Thanks to Janiis for the answer, my solution was:
in MainComponent
...
const refs = data.reduce((acc, value) => {
acc[value.id] = React.createRef();
return entry;
}, {});
const _onClick = id => {
refs[id].current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
....
then i passed it through to the child and referred like
<div ref={refs[entry.id]}>

Resources