I'm fetching from an API where I want to pass the response object as a prop onto a child component from App.js and use it in my Tags.js file. However, it only works after one time and when I refresh it, it gives me an error saying the props.response.names.length is undefined. I tried using the useEffect function to try and update it but it didn't work. I would appreciate any help.
My App.js file (still some remnants of when you run "npx create-react-app my-app"):
import './App.css';
import Tags from './Tags.js';
import React, { useState, useEffect } from 'react';
function App() {
const makeRequest = async () => {
try {
let response = await fetch('RANDOM_API_URL');
let json = await response.json();
setResponse(json);
} catch (error) {
console.log(error);
}
}
const [response, setResponse] = useState(makeRequest);
useEffect(() => {
setResponse(makeRequest);
}, []);
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
<Tags response={response}></Tags>
</div>
);
}
export default App;
My Tags.js:
import './App.js';
function Tags(props) {
const makeButtons = () => {
let result = [];
for (let i = 0; i < props.response.names.length; i++) {
result.push(<button key={i}>hello</button>);
}
return result;
}
return (
<div>
{makeButtons()}
</div>
);
}
export default Tags;
Your makeRequest function is async, and sets state internally. The reason you get this bug is that you don't always have a response.names to read length from - you only have an empty response object.
Either make sure you always have the names array available in state, or avoid rendering your Tags component when names is not present in state.
Also, try to avoid being creative with your dependency array, it's there for a reason. I see why you didn't include makeRequest in it though, since you create a new function on every render. That's something to keep in mind when you transition from class components to functional components. Class methods are stable across renders, functions declared in a functional component are not. To mimic a class method, you can declare functions using the useCallback hook, but again you need to include the dependency array. In your case, you can just create the async function inside useEffect, and then call it.
const [response, setResponse] = useState({names: []});
// or const [response, setResponse] = useState();
// and then, in Tags:
// function Tags({response = {names: []}) {
useEffect(() => {
const makeRequest = async () => {
try {
let response = await fetch('RANDOM_API_URL');
let json = await response.json();
setResponse(json);
} catch (error) {
console.log(error);
}
}
makeRequest();
}, []);
Supplying a default response prop to Tags will make sure you can read the length of names even before you have a response.
Looking more closely on your Tags component, I think it should be something like this:
// don't import App.js here
export default function Tags({response = {names: []}){
return (
<div>
{response.names.map(name => {
return <button key={name}>Hello {name}</button>
})}
</div>
)
}
Don't use index as key, that will cause problems if you rearrange the names in your array. And I guess you want to supply an onClick function to your buttons.
Related
i have the next code, the app should get a json from an api and then pass the entire json as a prop so it can be treated in each component file and return the element to the screen.
The thing is i haven't found a way to just store the info from the fetch into a variable. if i just pass a json that is declared in the same file it would work just fine but can't make it work with the info from the fetch, the developer tools shows the json is being retrieved but it doesn't seem to accesed in the components, as you can see in the components part i tried to use globalData to use the json store in that variable but it breaks my app and the console says "Uncaught TypeError: Cannot read properties of undefined (reading 'name')" in which name is is the first element inside my json.
import logo from './logo.svg';
import './App.css';
import React, {useEffect, useState} from 'react';
import Title from "./components/Title"
import Summary from "./components/Summary"
import Skills from './components/Skills';
import Experience from './components/Experience';
import Sideprojects from './components/Sideprojects';
import Education from './components/Education';
import Interests from './components/Interests';
import Courses from './components/Courses';
import Picture from './components/Picture';
import ReactDOM from "react-dom";
var cvdata = here i have a json with a lot of info, if i just write props = {cvdata} it would work just fine
function App() {
// fetch('http://localhost:5000/api')
// .then(res => console.log(res))
let dataGlobal;
const getData = async () => {
const response = await fetch("http://localhost:5000/api");
const data = await response.json();
dataGlobal = data;
return data;
};
(async () => {
await getData();
console.log(dataGlobal);
})();
return(
<div className="App">
<div className='Upleft'>
<Title props = {dataGlobal}/>
<Summary props = {cvdata}/>
<Experience props = {cvdata}/>
<Education props = {cvdata}/>
</div>
<div className='Right'>
<Picture props = {cvdata}/>
<Skills props = {cvdata}/>
<Interests props = {cvdata}/>
<Courses props = {cvdata}/>
<Sideprojects props = {cvdata}/>
</div>
</div>
)
}
export default App;
Maybe something like this would help:
It can look kinda funky, but essentially the hook useEffect takes an array of states, which when change, fires the containing function. I pass an empty array as the second argument of useEffect to have the fetch for data only happen the first load of the component. The first argument is the function you want to fire, which MUST return undefined. async functions always return a promise, so to work around this, we instead nest an anonymous async function we call immediately so the async / await syntax is available to us.
hopefully the code example is clear enough. If you have any questions or I misunderstood your prompt, please let me know. Good luck! 👍👍
const { useState, useEffect } = React;
function App (props) {
const [ page, setPage ] = useState("loading");
useEffect(() => {(async () => {
const res = await fetch(someWebsite)
.catch(err => setPage(err.message));
if (!res) return;
const txt = await res.text();
setPage(txt);
})();}, []);
return (
<div>
This is the page: <br />
{ page }
</div>
);
}
The issue I got is that the fetched Data from API is not saved to a variable. Please look at the fearvalue, it's being called later and the value of that is an empty string.
APY component
export let fearvalue = [];
export const fearAndGreed = () => {
// 1. Create a new XMLHttpRequest object
let bitcoinAPY = new XMLHttpRequest();
// 2. Configure it: GET-request for the URL /article/.../load
bitcoinAPY.open("GET", "https://api.alternative.me/fng/?limit=10&date_format=us", false)
bitcoinAPY.onload = () => {
const data = JSON.parse(bitcoinAPY.response);
/*const saveStaticDataToFile = () => {
let blob = new Blob(['Welcome'],
{type: 'text/plain;charset=utf-8'});
saveStaticDataToFile(blob, "static.txt")
}*/
console.log(data)
fearvalue = data.data[0];
}
// 3. Send the request over the network
bitcoinAPY.send();
}
window.addEventListener('load', fearAndGreed)
fearvalue is being called in this component and it is a blank value. Can anyone help me with saving data to this variable?
import './App.css';
import './Apy_TAPI';
import './Bitcoin Fear&Greed';
import { DataFormatting } from './DataFormatting.js';
import { fearvalue } from './Bitcoin Fear&Greed';
import './App.css';
import './Apy_TAPI';
import './Bitcoin Fear&Greed';
import { DataFormatting } from './DataFormatting.js';
import { fearvalue } from './Bitcoin Fear&Greed';
function App() {
const test1 = "test1"
console.log(fearvalue)
return (
<div className="App">
<header className="App-header">
<p>
Bitcoin analyst tool
</p>
</header>
<div className='Text'>
<h1>
<img className="Image" src="https://alternative.me/crypto/fear-and-greed-index.png" alt="Latest Crypto Fear & Greed Index" />
</h1>
<h2>
https://bitinfocharts.com/pl/bitcoin/address/1P5ZEDWTKTFGxQjZphgWPQUpe554WKDfHQ <br />
<br />
{fearvalue} <br />
</h2>
</div>
</div>
);
}
export default App;
You need to save the response in a proper way. in React.js, you could use useState to create a state variable and set the response in it.
First, you need to save the response in a state variable:
import React, {useState} from 'react';
export const fearAndGreed = () => {
const [fearValue, setFearValue] = useState() // initialize with proper value according to your data type, it could be a empty array [] or an object {}.
let bitcoinAPY = new XMLHttpRequest();
bitcoinAPY.open("GET", "https://api.alternative.me/fng/?limit=10&date_format=us", false)
bitcoinAPY.onload = () => {
const data = JSON.parse(bitcoinAPY.response);
setFearValue(data.data[0]) // ------> set the fearValue here
}
bitcoinAPY.send();
}
window.addEventListener('load', fearAndGreed)
So far, the first part is done. but you need the fearValue in the other component. to achieve this, there are some solutions like using a Global State Manager like Redux or ContextApi. without them, your implementation would be tricky since you can't use the lifting state up technique because you didn't use fearAndGreed as a child component in the parent component (App).
In such cases, you can implement a custom hook with fearAndGreed function. since the function invokes once after the page loading, you can implement this by calling the API after your components did mount.
Let's make a custom hook with the current fearAndGreed function in this way:
import {useEffect, useState} from 'react';
export const useFearAndGreed = () => {
const [fearValue, setFearValue] = useState();
useEffect(() => {
let bitcoinAPY = new XMLHttpRequest();
bitcoinAPY.open("GET", "https://api.alternative.me/fng/?limit=10&date_format=us", false)
bitcoinAPY.onload = () => {
const data = JSON.parse(bitcoinAPY.response);
setFearValue(data.data[0]) // ------> set the fearValue here
}
bitcoinAPY.send();
}, [])
return fearValue;
}
Explanation:
With a few changes, fearAndGreed function becomes a custom hook useFearAndGreed.
The API will call in the useEffect after the component did mount (with an empty array of dependencies).
The hook will return the fearValue on every change.
Now, time to use this custom hook inside of the App component:
function App() {
const fearValue = useFearAndGreed()
return (
<div>
{fearValue}
</div>
);
}
export default App;
Note: I removed the other parts of implementation in the App component to simplify it. you should include your own implementation within.
Now, every time the App component did mount, the useFearAndGreed will be invoked and return the fearValue which can be saved in a constant fearValue to use in the div element.
I'm confused about how to use localStorage to persist the data that's coming from calling the API.
I want whenever I refresh the page, the callApi inside useEffect to not render new data and keep the existing data unchanged.
Any help would be appreciated.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { Layout, Loading, OverviewHeader, OverviewSubHeader, SiteCard } from '../components';
const Overview = () => {
const [loading, setLoading] = useState(true);
const [sites, setSites] = useState([]);
useEffect(() => {
async function callApi() {
const response = await axios.get(`https://randomuser.me/api/?results=3`);
const sites = response?.data?.results;
console.log('sites', sites);
setSites(sites);
await localStorage.setItem('sites', JSON.stringify(sites));
setLoading(false);
}
callApi();
}, []);
return (
<div>
<Layout>
<OverviewHeader />
<OverviewSubHeader />
<div className='overview-page-wrapper'>
{loading ? (
<Loading />
) : (
sites.map(site => {
return (
<React.Fragment>
<SiteCard
key={site.login.uuid}
siteId={site.login.uuid}
image={site.picture.large}
firstName={site.name.first}
lastName={site.name.last}
city={site.location.city}
country={site.location.country}
sensors={site.dob.age}
notifications={site.registered.age}
latitude={site.location.coordinates.latitude}
longitude={site.location.coordinates.longitude}
{...site}
/>
</React.Fragment>
);
})
)}
</div>
</Layout>
</div>
);
};
export default Overview;
I'm not too sure what you're trying to accomplish, seeing as you'd likely want to refresh that data at some point.
Maybe you could indicate what behaviour/scenario you're trying to cater for?
In any case, to answer your question, what you could do is smth like:
const [displayedSites, setDisplayedSites] = useState([])
// this does both setting the state for your UI
// and stores to localStorage
const setAndSaveDisplayedSites = (fetchedSites) => {
setDisplayedSites(sites)
localStorage.setItem('sites', JSON.stringify(sites))
}
useEffect(() => {
(async function () {
const localSites = localStorage.getItem(sites);
if (!localSites) {
// this will only ever fetch if it is your first time mounting this component
// I suppose you would need to call setAndSaveDisplayedSites
// from a "refresh" button
const fetchedSites = await getSitesFromAPI()
setAndSaveDisplayedSites(fetchedSites)
return
}
const parsedLocalSites = JSON.parse(localSites)
setDisplayedSites(parsedLocalSites)
})()
}, [])
also checkout this hook that takes care of some things for you: https://usehooks.com/useLocalStorage/
Use the useContext hook for this purpose OR if you really just want to use the local storage anyhow, then use it but manage different states/variables for that.
Your current state (that you want to render on the screen)
Your fetched data (the one that you want to keep)
Hope this makes sense. Thankyou!
So I have this application that displays random quotes that are pulled as JSON data from an API. It's my first foray into React so it is not really well done. Initially, I had all of my code stored in one component - but this was obviously not best practices because I had multiple things that could be split into components, i.e. a quote, a footer, a share button.
The issue I ran into when I split it up is that I didn't know how to share state between component files (for sharing to Twitter or other additional features) because I fetch the data like this:
/* this function accesses the API and returns a json */
export default function fetchQuote() {
return fetch('https://programming-quotes-api.herokuapp.com/quotes/random') // fetch a response from the api
.then((response) => {
let json = response.json(); // then assign the JSON'd response to a var
return json; // return that bad boy
});
}
which originally was called within the component class like so:
/* component for the quotes */
export default class Quote extends React.Component {
/* placeholder */
constructor(props) {
super(props);
this.state = {
quoteAuthor: "Rick Osborne",
quote: "Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live."
}
}
/* actually render things */
render() {
return (
<div className="quotes">
<h1>{this.state.quoteAuthor}</h1>
<p>{this.state.quote}</p>
<div className="button">
<button id="button" onClick={this.update}>New quote</button>
</div>
</div>
);
}
/* async fetch the quotes and reassign the variables to them once processed */
update = async() => {
let response = await fetchQuote();
console.log(response);
this.setState({
quoteAuthor: response.author,
quote: response.en
});
};
}
From my understanding, React's hooks seemed to solve my problem because I could use useState and useEffect which I tried to implement as follows (with the original fetchQuote() function untouched):
export default function Quote() {
const [author, setAuthor] = useState("Rick Osborne");
const [quote, setQuote] = useState(
"Always code as if the guy who ends up maintaining your code will be a violent psychopath who knows where you live."
);
let json = fetchQuote();
useEffect (() => {
setAuthor(json.author);
setQuote(json.quote);
console.log(json);
});
return (
<div className="quotes">
<h1>{author}</h1>
<p>{quote}</p>
<div className="button">
<button id="button" onClick={async () => json = await fetchQuote()}>
New quote
</button>
</div>
</div>
)
}
However, no errors are thrown except the area in which the quote is displayed shows empty and calling console.log(json) within the useEffect simply returns
Promise { <state>: "pending" }
Promise { <state>: "pending" }
Am I using Hooks properly? How can I properly update the state with the JSON data?
It looks like the promise from fetch isn't resolving.
Try this:
export default Quote = () => {
const [author, setAuthor] = useState("Rick Osborne");
const [quote, setQuote] = useState('');
const fetchMyAPI = async () => {
let json = await fetchQuote();
setAuthor(json.author);
setQuote(json.quote);
}
useEffect(() => {
fetchMyAPI();
}, []);
return (
<div className="quotes">
<h1>{author}</h1>
<p>{quote}</p>
<div className="button">
<button id="button" onClick={fetchMyAPI}>
New quote
</button>
</div>
</div>
)
This called fetchMyAPI onMount, and calls it whenever you click on New Quote.
I am new to React and GraphQL. Trying to update React state with GraphQL subscription feed but it generates the update depth error.
Here is the simplified code:
import { Subscription } from 'react-apollo';
...
function Comp() {
const [test, setTest] = useState([]);
const Sub = function() {
return (
<Subscription subscription={someStatement}>
{
result => setTest(...test, result.data);
return null;
}
</Subscription>
);
};
const Draw = function() {
return (
<div> { test.map(x => <p>{x}</p>) } </div>
);
};
return (
<div>
<Sub />
<Draw />
<div/>
);
};
export default Comp;
Regular query works fine in the app and the Subscription tag returns usable results, so I believe the problem is on the React side.
I assume the displayed code contains the source of error because commenting out the function "Sub" stops the depth error.
You see what happens is when this part renders
<Subscription subscription={someStatement}>
{
result => setTest(...test, result.data);
return null;
}
</Subscription>
setTest() is called and state is set which causes a re-render, that re-render cause the above block to re-render and setTest() is called again and the loop goes on.
Try to fetch and setTest() in your useEffect() Hook so it does not gets stuck in that re-render loop.
useEffect like
useEffect(() => {
//idk where result obj are you getting from but it is supposed to be
//like this
setTest(...test, result.data);
}, [test] )
Component Like
<Subscription subscription={someStatement} />