Showing data from state variable in ReactJS forms infinite loop - javascript

I'm trying to show data from an API call. The structure of the application looks like
MainComponent -> RefreshButton (this will fetch the data)
MainComponent -> ShowData (this will show the data that is being fetched)
MainComponent has a state userData that will store the response that was received from the API. Now the issue is, whenever I'm clicking the button, it is getting into an infinite loop of rendering and calls the API infinite times.
This is what the error shows:
Here is my MainComponent -
import React, { useEffect, useState } from "react";
import RefreshButton from "./RefreshButton";
import ShowData from "./ShowData";
const MainComponent = () => {
const [userData, setUserData] = useState();
useEffect(() => {
console.log(userData);
}, [userData]);
return (
<div>
<p style={{ textAlign: "center" }}>Main Component</p>
<RefreshButton setUserData={setUserData} />
{userData && <ShowData userData={userData} />}
</div>
);
};
export default MainComponent;
Here is my RefreshButton component -
import React from "react";
import axios from "axios";
const RefreshButton = ({ setUserData }) => {
const getData = () => {
axios
.get(`https://jsonplaceholder.typicode.com/todos`)
.then((response) => {
if (response.status === 200) setUserData(response.data);
})
.catch((err) => {
console.log(err);
});
};
return (
<div className="button-container">
<button className="fetch-data-button" onClick={() => getData()}>
Fetch new data
</button>
</div>
);
};
export default RefreshButton;
And here is my ShowData component -
import React from "react";
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info}
</div>
))}
</>
);
};
export default ShowData;
PS - I'm new to React and couldn't find a potential solution on this, there are several tutorials on how to fetch data from API calls and show it, but I wanted to know what I'm doing wrong here. Thanks in advance!

You might have misunderstood with the infinite loop error
It's actually a render error as being shown here:
To fix your render error, simply put an actual string variable in the {}
Because the response was an array of this object, so you can't simply render the whole object but need to pick an actual string variable inside:
[{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}],
Change to something like this:
const ShowData = ({ userData }) => {
console.log("Here", userData);
return (
<>
{userData.map((info, idx) => (
<div className="user-data" key={idx}>
{info.title} // <-- Put a title here.
</div>
))}
</>
);
};

Remove
useEffect(() => {
console.log(userData);
},[userData])
This will reevaluate component whenever user data changes, which Leeds to call showData infinitely

Related

Passing a second value through to js file

I am slowly learning JS & React, and trying to build an app that pulls weather data from various locations and displays it on a Monday.com dashboard. Still a very long way to go, but putting the puzzle pieces together 1 by 1.
I have a working app that gets the weather based on lattitude & longitude, and then displays it on the page.
Here is my code for App.js
import './App.css';
import React, { useEffect, useState } from "react";
import { Dimmer, Loader } from 'semantic-ui-react';
import Weather from './components/weather';
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
// this useEffect will only fire when the component mounts (once)
useEffect(() => {
navigator.geolocation.getCurrentPosition(location => {
setLat(location.coords.latitude);
setLong(location.coords.longitude);
});
}, [])
// this useEffect will be called once the component mounts + when any dependency changes (lat/long)
useEffect(() => {
fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result_geo => {
setData(result_geo)
console.log("Latitude is:", lat) // (GL)
console.log("Longitude is:", long) // (GL)
console.log(result_geo);
});
}, [lat, long])
return (
<div className="App">
{(typeof data.main != 'undefined') ? (
<Weather weatherData={data}/>
): (
<div>
<Dimmer active>
<Loader>Loading..</Loader>
</Dimmer>
</div>
)}
</div>
);
}
And here is my code for weather.js
import React from 'react';
import '../styles.css';
import { Card } from 'semantic-ui-react'
import moment from 'moment';
const CardExampleCard1 = ({weatherData}) => (
<div className="Cards">
<Card className="card1">
<Card.Content>
<Card.Header className="header">Location: {weatherData.name}</Card.Header>
<p>{moment().format('dddd')}, {moment().format('LL')} {moment().format('HH:mm')}</p>
<br></br>
<p>Temperature: {weatherData.main.temp} °C</p>
<p>Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}</p>
<p>Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}</p>
<p>Description: {weatherData.weather[0].main}</p>
<p>Humidity: {weatherData.main.humidity} %</p>
<br></br>
</Card.Content>
</Card>
</div>
)
export default CardExampleCard1;
The next thing I am trying to figure out is how to display multiple cards (using { Card } from 'semantic-ui-react' ) for various different locations.
I am able to retrieve the weather for an additional location by duplicating the API call and changing the lat&lon (probably there's a better way), and I can store that into a variable, but I don't know how to pass that to weather.js in my return function. I have tried multiple things but it fails. And also how to update my weather.js code to then display different cards.
ANy help would be appreciated - I've been stuck on this for hours.
Eventually, I need to read location names from a monday.com board, do an API call to retrieve their geolocations, then API calls to get their weather, and then display the weather. And this can be for a variable number of locations.
You can use the map function on an array like this below. Assuming the data in state is an array, and the response from server is a JSON object, then when calling for the second time, your array is updating by adding new object in it.
<div>
{data.map(item => <Weather weatherData={item}/>)}
</div>
import './App.css';
import React, { useEffect, useState } from "react";
import { Dimmer, Loader } from 'semantic-ui-react';
import Weather from './components/weather';
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
// this useEffect will only fire when the component mounts (once)
useEffect(() => {
navigator.geolocation.getCurrentPosition(location => {
setLat(location.coords.latitude);
setLong(location.coords.longitude);
});
}, [])
// this useEffect will be called once the component mounts + when any dependency changes (lat/long)
useEffect(() => {
fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result_geo => {
setData(result_geo)
console.log("Latitude is:", lat) // (GL)
console.log("Longitude is:", long) // (GL)
console.log(result_geo);
});
}, [lat, long])
return (
<div className="App">
{(typeof data.main != 'undefined') ? (
<div>
{data.map(item => <Weather weatherData={item}/>)}
</div>
): (
<div>
<Dimmer active>
<Loader>Loading..</Loader>
</Dimmer>
</div>
)}
</div>
);
}
"display multiple cards": display arrays of Components in React. So to display multiple you can use Array.map().
Ex:
const people = [
{ name: 'Harry' },
{ name: 'Ron' },
{ name: 'Hermione' },
]
...
return (
<>
{people.map((e, idx) => {
return (<span key={idx}>{e.name}</span>)
})}
</>
)
"pass that to weather.js": Not sure to get it. But when your parent component update a state the childrens using that state are going to rerender. So using a setData() when you get your new api response should update weatherData in the child component.

Prevent losing data when refreshing on a different route - react

I wanted to prevent losing state on page refresh while being on a different route path. Im curious why the first example does not work. From what i understand when app mounts first thing that gonna render is component itself and then useEffects run. Since i got 3 here, first fetches and saves the data to the invoiceList state and then next useEffect that run should fill localStorage key with invoiceList state data. The last one obviously retrieve the data.
The second one does fill the "invoiceData" localStorage key with an empty array. Why is this happening if the invoiceList state already have the data after the first useEffect?
The second example that i provided works. I removed second useEffect and set localStorage key in the first useEffect with response data that i get from fetch.
I also wonder if im doing everything correct here. Any feedback appreciated :)
First example (not working):
import { ReactElement, useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
type Props = {};
const Root = (props: Props): ReactElement => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
setInvoiceList(data);
};
fetchData();
}, []);
useEffect(() => {
window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList));
}, []);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
Second example (working):
import { ReactElement, useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
type Props = {};
const Root = (props: Props): ReactElement => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
window.localStorage.setItem("invoiceData", JSON.stringify(data));
setInvoiceList(data);
};
fetchData();
}, []);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
The first example is never storing the data into the localStorage because the fetch is an asynchronous function that and you are writing basically always the empty array into your localStorage.
The order of execution in the first example will be:
fetchData called
window.localStorage.setItem("invoiceData", JSON.stringify(invoiceList)); <- still empty array
setInvoiceList(JSON.parse(window.localStorage.getItem("invoiceData") || "[]"));
response.json() called
setInvoiceList(data); called
I would also recommend to improve your code a little like that:
import React, { useEffect, useState } from "react";
import { Outlet } from "react-router-dom";
import { Bar } from "../../components/Bar/Bar";
import { Invoice } from "./Root.utils";
const Root: React.FC = () => {
const [invoiceList, setInvoiceList] = useState<Invoice[]>([]);
useEffect(() => {
setInvoiceList(
JSON.parse(window.localStorage.getItem("invoiceData") || "[]")
);
const fetchData = async () => {
const response = await fetch("./data.json");
const data = await response.json();
window.localStorage.setItem("invoiceData", JSON.stringify(data));
setInvoiceList(data);
};
fetchData();
}, []);
return (
<div>
<Bar />
<Outlet context={{ invoiceList }} />
</div>
);
};
export default Root;
You can use the Link component from react-router and specify to={} as an object where you specify pathname as the route to go to. Then add a variable e.g. data to hold the value you want to pass on. See the example below.
Using the <Link /> component:
<Link
to={{
pathname: "/page",
state: data // your data array of objects
}}
>
Using history.push()
this.props.history.push({
pathname: '/page',
state: data // your data array of objects
})
Using either of the above options you can now access data on the location object as per the below in your page component.
render() {
const { state } = this.props.location
return (
// render logic here
)
}

React Cant read fetched data after page refresh

So I have been trying to fetch this data from https://bad-api-assignment.reaktor.com/rps/history to my node.js back-end and display it on my React front-end.
I can somehow make it work and see the data at the console, but when refreshing the front-end page, I will get errors like this when trying to handle the data again:
App.js:
import axios from "axios";
import React from "react";
import GameData from "./GameData";
export default function App() {
const [games, getGames] = React.useState(null);
const baseURL = "http://localhost:5000";
React.useEffect(() => {
getAllGames();
}, []);
const getAllGames = async () => {
await axios.get(baseURL)
.then((response) => {
const allGames = response.data.data;
//console.log(allGames)
getGames(allGames);
})
.catch(error => console.error('Error: $(error'));
}
return(
<GameData games={games}/>
)
}
GameData.js:
import React from 'react';
export default function GameData(props) {
const displayGames = (props) => {
const {games} = props;
console.log(games)
games.map((game, index) => {
console.log(game, index);
return(
<div className='game' key={game.type}>
</div>
)
}
)
}
return(
<>
{displayGames(props)}
</>
)
}
On GameData.js, if I comment this section:
//games.map((game, index) => {
// console.log(game, index);
// return(
// <div className='game' key={game.type}>
// </div>
// )
//}
//)
I can see that console.log(games) at my console. Then I can un-comment those lines and save on React, and it will display mapped data on console:
Console after un-commenting code and saving on React.
Okay so perfect, it works so far as I wish, but if I refresh the page on my browser, I will face the error/null issue Console error messages after page refresh.
I have been trying to google that but could not figure it out. How to solve issue like this? I should be able to sort that data later as well and so on.
Hope it makes sense.
first check if array is not empty then loop through it:
//GameData.js
export default function GameData({ games }) {
return(
<>
{games.length > 0 && games.map((item) => (
<div key={item.id}>
{item.name}
</div>
))
</>
)
}

How to Re-render Component Only Once after the data is changed?

I am new to React JS. I am making CRUD Operation in React. Everything is fine but when I delete the item from the list I have to refresh the browser tho update the List. How can I solve this?
import React, { useState, useEffect } from 'react'
import axios from 'axios';
import { Segment, Item, Container, Card, Icon, Button } from 'semantic-ui-react';
import { IEmployee } from '../../src/Model/activity'
import { Link, RouteComponentProps } from 'react-router-dom';
interface DetailParams {
id: string;
}
const EmployeeList : React.FC<RouteComponentProps<DetailParams>> = ({ match, history }) => {
const [employees, setEmployees] = useState<IEmployee[]>([])
useEffect(() => {
axios.get('https://localhost:44353/Employee/GetEmployeeList')
.then((response) => {
setEmployees(response.data)
})
}, [])
const deleteEmployee =(id: string) => {
axios.get(`https://localhost:44353/Employee/DeleteEmployee/${id}`)
.then((response) => {
history.push('/employeeList')
})
}
return (
<Container style={{ marginTop: '7em' }}>
<Segment>
{
employees.map(employee => (
<Card key={employee.id}>
{/* <Image src='/images/avatar/large/daniel.jpg' wrapped ui={false} /> */}
<Card.Content>
<Card.Header>{employee.firstName}</Card.Header>
<Card.Meta>{employee.address}</Card.Meta>
<Card.Description>
{employee.organization}
</Card.Description>
</Card.Content>
<Card.Content>
<Button
onClick={() => deleteEmployee(employee.id)}
floated="right"
content="Delete"
color="red" />
<Button
as={Link} to={`/edit/${employee.id}`}
floated="right"
content="View"
color="blue" />
</Card.Content>
</Card>
))
}
</Segment>
</Container>
)
}
export default EmployeeList
The above code is of EmployeeList Component which is routed by ** /employeeList ** . Here is the UI of the code
when I delete the item from the list I need to reload the browser to update the List. I tried using employee dependent in useEffect
useEffect(() => {
axios.get('https://localhost:44353/Employee/GetEmployeeList')
.then((response) => {
setEmployees(response.data)
})
}, [employees])
this worked fine but the API method is executing infinitely. How do I solve this?
Two things can be done
if your delete api returns the updated data you can just call setEmployess and set the updated value .
or you can filter the deleted value from the state employees
const deleteEmployee =(id: string) => {
//add this in axios call success
let updatedEmployee = [...employees];
updatedEmployee.filter(eachEmployee=>eachEmployee.id !== id);
setEmployees(updatedEmployee);
}
Instead of refreshing the page you should just make another request after the delete request to get an updated employees list.
const deleteEmployee = async (id: string) => {
// Delete employee
await axios.get(`https://localhost:44353/Employee/DeleteEmployee/${id}`)
// Get a fresh list
const employees = (await axios.get('https://localhost:44353/Employee/GetEmployeeList')).data
setEmployees(employees)
// Navigate
history.push('/employeeList')
}

Cleanup function with hooks

I'm making a project with an API call, and so far I've been able to pass the static data(for know I'll keep it hard coded) and then console.log the data provided by the static data, but I can't store it in my state, I can just console.log it and I dont know why. The following error happens in my console:
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Here's the code, I think you guys will see what I'm doing in a better way:
import React, { useState } from "react";
import "./styles.css";
import TopList from "./components/TopList";
export default function App() {
const [state, setState] = useState({
data: [23251319, 23251742, 23251158, 2423431],
results: []
});
const fetcher = id => {
fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`)
.then(res => res.json())
.then(data => {
console.log(data);
setState({
results: data
});
});
};
return (
<div>
<TopList data={state.data} fetcher={fetcher} />
</div>
);
}
import React from "react";
import Top from "./Top";
function TopList({ data, fetcher }) {
const mapped = data.map(item => (
<Top fetcher={fetcher} id={item} key={item} />
));
return <div>{mapped}</div>;
}
export default TopList;
import React from "react";
function Top({ id, fetcher }) {
fetcher(id);
return (
<div>
<h1>Hello from top</h1>
</div>
);
}
export default Top;
You should be fetching the data after the component has mounted inside a componentDidMount() lifecycle method or since you are using functional components you can use the useEffect() hook.
Secondly you are prop drilling the fetcher to the Top component for no reason.
If the Top component fetches the data, it should be responsible for calling the fetcher inside a useEffect() hook.
For example
in your app component
export default function App() {
const [ids, setIds] = useState([23251319, 23251742, 23251158, 2423431]);
return (
<div>
<TopList idArray={ids}/>
</div>
);
}
in TopList
function TopList({ idArray }) {
return (
<div>
{
idArray.map((id) => (<Top id={id} key={id}/>))
}
</div>;
}
In Top Component
function Top({ id }) {
const [state, setState] = useState({results: null, error: undefined})
useEffect(() => {
const fetcher = id => {
fetch(`https://hacker-news.firebaseio.com/v0/item/${id}.json?print=pretty`)
.then(res => res.json())
.then(data => {
console.log(data);
// if fetch success
setState({
results: data,
error: undefined
});
})
.catch(error => {
// if error set results to null and error to the error that happened
// during fetch
setState({results: null, error: error})
});
};
// finally call the fetcher with the id
fetcher(id);
}, [id])
return (
<div>
<h1>Hello from top</h1>
<pre>{state.results && JSON.stringify(state.results, null, 2)}</pre>
</div>
);
}

Categories

Resources