How to render based on AJAX result in react js? - javascript

Just want to render movie cards based on results that come from ajax call.
Currently, the movie cards components are rendered based on that hard code array named list. I just want to make it dynamic and replace it with my ajax data.
const getlist = async () => {
const res = await fetch('http://localhost:3001/customize');
const data = await response.json();
getlist();
};
export default function Index() {
const list = ['dexter', 'bb', 'got'];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{test.map((element, i) => {
return <MovieCard movieName={element} key={i} />;
})}
</div>
</main>
</>
);
}

Use the useState hook to set up your component state (the list) and fetch data in a useEffect hook...
The Effect Hook lets you perform side effects in function components:
Data fetching, setting up a subscription, and manually changing the DOM in React components are all examples of side effects. Whether or not you’re used to calling these operations “side effects” (or just “effects”), you’ve likely performed them in your components before.
import { useEffect, useState } from "react"
const getlist = async () => {
const res = await fetch("http://localhost:3001/customize")
if (!res.ok) {
throw new Error(`${res.status}: ${await res.text()}`)
}
return res.json()
}
const Index = () => {
const [ list, setList ] = useState([]) // start with an empty array
useEffect(() => {
getList()
.then(setList)
.catch(console.error)
}, []) // empty dependencies array, this runs only once
return (
// ...
{list.map((element, i) => (
<MovieCard movieName={element} key={i} />
))}
// ...
)
}
export default Index

Related

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
)
}

ReactJS how to update page after fetching data

I'm new to ReactJS and I'm now trying to do an interactive comments section (taken from frontendmentor.io), but the App component just doesn't show what it's supposed to show
This is my App component:
function App() {
const [data, setData] = useState([]);
useEffect(() => {
const getComm = async () => {
await fetchData();
};
getComm();
}, []);
console.log(data);
const fetchData = async () => {
const res = await fetch("db.json").then(async function (response) {
const comm = await response.json();
setData(comm);
return comm;
});
};
return (
<Fragment>
{data.length > 0 ? <Comments data={data} /> : "No Comments to Show"}
</Fragment>
);
}
export default App;
The console.log(data) logs two times:
the first time it's an empty Array;
the second time it's the Array with my datas inside.
As it follows:
If I force the App to print the Comments it just says that cannot map through an undefined variable
This is my Comments component:
function Comments({ data }) {
return (
<div>
{data.map((c) => (
<Comment key={c.id} />
))}
</div>
);
}
export default Comments;
I'm wondering why the page still displays No Comments to Show even if the log is correct
#Cristian-Irimiea Have right about response get from fetch. Response is an a object and can't be iterate. You need to store in state the comments from response
But you have multiple errors:
Take a look how use async function. Your function fetchData looks bad.
// Your function
const fetchData = async () => {
const res = await fetch("db.json").then(async function (response) {
const comm = await response.json();
setData(comm);
return comm;
});
};
// How can refactor
// fetchData function have responsibility to only fetch data and return a json
const fetchData = async () => {
const response = await fetch("./db.json");
const body = await response.json();
return body;
};
You are updating state inside fetch function but a good solution is update state then promise resolve:
useEffect(() => {
// here we use .then to get promise response and update state
fetchData().then((response) => setData(response.comments));
}, []);
The initial state of your data is an array.
After you fetch your data from the response you get an object. Changing state types is not a good practice. You should keep your data state as an array or as an object.
Considering you will keep it as an array, you need use an array inside of setData.
Ex.
comm && Array.isArray(comm.comments) && setData(comm.comments);
As for your Comments component you should consider expecting an array not an object.
Ex.
function Comments(data) {
return (
<div>
{data.map((c) => (
<Comment key={c.id} />
))}
</div>
);
}
export default Comments;

How to await data coming from an API as a prop in a component?

I am trying to send a prop on a component once my data loads from an API. However, even though I am using async await to await from my data, I am still getting an error of: Error: Objects are not valid as a React child (found: [object Promise]). If you meant to render a collection of children, use an array instead.
My process:
Have two set states: 1) updateLoading and 2) setRecordGames
Once the data is loaded, I will set updateLoading to false and then set recordGames with the data from the API.
My Problem:
It seems that when I pass data in the component, React does not wait for my data load and gives me an error.
This is my code:
import useLoadRecords from '../../hooks/useLoadRecords'
import { CustomPieChart } from '../charts/CustomPieChart'
export const GameDistribution = () => {
const { records, loading } = useLoadRecords()
let data = records
console.log(data) // This logs out the records array
return (
<div>
// once loading is false, render these components
{!loading ? (
<>
<div>
{recordGames.length > 0 ? (
// This line seem to run as soon as the page loads and I think this is the issue. recordGames is empty at first, but will populate itself when the data loads
records.length > 0 && <CustomPieChart data={data} />
) : (
<div>
No games
</div>
)}
</div>
</>
) : (
<span>Loading...</span>
)}
</div>
)
}
// useLoadRecords.js
import { useState, useEffect } from 'react'
import { API } from 'aws-amplify'
import { listRecordGames } from '../graphql/queries'
// Centralizes modal control
const useLoadRecords = () => {
const [loading, setLoading] = useState(false)
const [records, updateRecords] = useState([])
useEffect(() => {
fetchGames()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
const fetchGames = async () => {
try {
let recordData = await API.graphql({
query: listRecordGames,
})
setLoading(false)
let records = recordData.data.listRecordGames.items.map(
(item) => item.name
)
let result = Object.values(
records.reduce((acc, el) => {
if (!acc[el]) acc[el] = { name: el, plays: 0 }
acc[el].plays++
return acc
}, {})
)
updateRecords(result)
} catch (err) {
console.error(err)
}
}
return { records, loading }
}
export default useLoadRecords
I would make a hook for the data and setData to it when fetching ,
you can clone the data using spread operator and this pass it.
or better return that component only if there is something in data for example
{
data && <CustomPieChart data={recordGames} />
}
and it would be nice to make a some loader (gif/svg) using your loading hook so it can be shown when the data is still being fetched.

React Asynchronous Fetching

Using React and React-Dom CDN 16
I am new to React and trying to build a dashboard component that takes the value of one of three buttons in a Buttons component and sends the value to a List component. The List component fetches data from an API and renders the results.
The feature works fine up until the data fetching, which it only does once the app is rendered the first time. I've logged that the state that's set by the Buttons component is making its way to the List component and the fetch action is updating dynamically correctly, but the fetching functionality isn't getting triggered when that state updates.
Here's the code.
const { useState, useEffect } = React
const App = props => {
return (
<div className="app-content">
<Dashboard />
</div>
);
};
const Dashboard = props => {
const [timespan, setTimespan] = useState('week');
const changeTime = time => setTimespan(time);
return(
<div>
<p>{timespan}</p> // this state updates when the buttons are clicked
<Buttons onUpdate={changeTime} />
<List timespan={timespan}/>
</div>
);
};
const Buttons = props => {
return (
<div>
<button onClick={props.onUpdate.bind( this, 'week' )}>
week
</button>
<button onClick={props.onUpdate.bind( this, 'month' )}>
month
</button>
<button onClick={props.onUpdate.bind( this, 'all' )}>
all
</button>
</div>
);
};
const List = props => {
const timespan = props.timespan;
const homepage = `${location.protocol}//${window.location.hostname}`;
const action = `${homepage}?fetchDataset=1&timespan=${timespan}`;
// if I console.log(action) here the URL is updated correctly
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [obj, setObj] = useState([]);
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
(result) => { // if I console.log(result) here I only get a response at initialization
setIsLoaded(true);
setObj(result);
},
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, []);
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<div>
// my API returns "timespan is: $timespan", but this is only ever "week" because that's the initial state of the timespan
{obj}
</div>
);
};
};
ReactDOM.render(
<App />,
document.getElementById('app')
);
I think I must be overlooking something very obvious because this seems like one of the core purposes of React, but it's hard to find documentation that is relevant with version 16 updates like function classes and hooks.
I really appreciate any help. Thanks!
you need to add timeSpan (or action) to your useEffect dependency array:
useEffect(() => {
fetch(action)
.then(res => res.json())
.then(
result => {
setIsLoaded(true);
setObj(result);
},
error => {
setIsLoaded(true);
setError(error);
}
);
}, [timeSpan]); // [action] will also solve this
This way the effect will know it needs to run every time the timeSpan prop changes.
By passing an empty dependency array you are telling the effect to only run once - when the component it mounted.

simple fetching api with hooks and printing?

I'm trying to import a card API using react hooks and print the cards to the browser but I can't figure out how?
This is the structure of the api
Im trying to use the .map function to access the "image"
import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
const App = () => {
const [deck, setDeck] = useState ([]);
useEffect(async () => {
const response = await fetch('https://deckofcardsapi.com/api/deck/new/draw/?count=5')
const data = await response.json();
setDeck(data);
console.log(data)
}, []);
return (
<ul>
{deck.map(a => (<li> {a.cards.image}</li>))} // how to access api's "image" ??
</ul>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
Vicente, have a look at this CodeSandbox to see an example of how you could implement it. And here is the App component:
const App = () => {
const [deck, setDeck] = React.useState({ cards: [] });
React.useEffect(() => {
async function getData() {
const response = await fetch(
"https://deckofcardsapi.com/api/deck/new/draw/?count=5"
);
const data = await response.json();
setDeck(data);
console.log(data);
}
getData();
}, []);
return (
<div className="App">
{deck.cards.map((card, index) => (
<div key={index}>
<img src={card.image} alt={card.code} />
</div>
))}
</div>
);
};
An important thing to take into account is that React.useEffect should run synchronously. Instead of passing an async function into it, define an async function inside the useEffect callback, and run it synchronously. Inside this new function you can await for promises to resolve, and you can set the state inside this function. Please read "React's useEffect" docs for more information
Another problem you had was that you were instantiating the deck state with an empty array ([]). But, the response is an object, so when you updated your state you got an error. I solve that by initializing the deck state with an object that defined a cards list, following the result of your endpoint.

Categories

Resources