I have a scenario where i want to first make a request and validate the response from the server and then show UI. Complete Response also needs to be passed in the component. I have sample code and i got the proper response from server but it does not get passed to the component. I am using props to pass data to Component. How can i achieve this ?
index.tsx
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
App.tsx
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { CarsGrid } from "./CarsGrid";
const init = async () => {
try {
const response = await fetch(
"https://www.ag-grid.com/example-assets/row-data.json"
)
return await response.json();
} catch (err) {}
};
function App() {
init().then(result=> {
console.log('Correct Response is printed',result);
return <CarsGrid gridRows={result} />;
});
return (<></>)
}
export default App;
React Component
export function CarsGrid({gridRows}) {
console.log('Data gridRows', gridRows);
})
But results does not get printed in console.log('Data gridRows', gridRows); though response is printed at console.log('Correct Response is printed',result);
Any help is appreciated.
The return in your code:
init().then(result=> {
console.log('Correct Response is printed',result);
return <CarsGrid gridRows={result} />;
});
is returning for the callback passed to then, not as the rendering result of your App function/component.
So the init().then() part is not contributing to the rendering process. This is as if your App code was:
function App() {
return (<></>)
}
And so your CarsGrid function is never executed.
A "react" way would be to rely on a state and to fetch your data in an effect:
function App() {
const [data, setData] = useState();
useEffect(() => {
const init = async () => {
try {
const response = await fetch(
"https://www.ag-grid.com/example-assets/row-data.json"
);
const result = await response.json();
console.log('Correct Response is printed',result);
setData(result);
} catch (err) {}
};
init();
}, []);
return data && <CarsGrid gridRows={data} />;
}
Related
Hello my code is as follow wnna try suspense with react 18 but not having luck to render Loading....
User.jsx
import React, { useEffect, useState } from "react";
export const User = () => {
const [user, setuser] = useState(null);
useEffect(() => {
const res = fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => setuser(json));
}, []);
return <div>{JSON.stringify(user)}</div>;
};
App.js
import logo from './logo.svg';
import './App.css';
import { Suspense } from 'react';
import { User } from './components/User';
function App() {
return (
<div className="App">
<Suspense fallback={<p>Loading.....</p>}>
<User/>
</Suspense>
</div>
);
}
export default App;
the app renders {} or null but never getting the fallback as expected
Suspense is a very smart paradygm based on fetch-while-render strategy. This means that React Components from now on are able to consume directly asynchronous data. And will render the fallback while the data promise is not resolved.
The only issue is that you can't pass directly a promise, but you need a wrapper that transforms it into a Suspense consumable entity. Basically the React Component wrapped in Suspense tags, will start to try to render continuosly and it expects for a method that throws a new promise until the original promise is not resolved, that's how it knows that it has to keep rendering the fallback. So you need to pass a resource with a very specific shape, that's why you need a wrapper.
Fetching libraries like react-queris or RTK-query will implement the wrapper themselves, so you won't have to care of that part, it's not something you will have to do manually, but still if you'd like to see what's under the hood, this would be a very basic implementation of a Suspense ready fetching library:
import React, { useEffect, useState, Suspense, useRef } from 'react';
export default function App() {
return (
<div className="App">
<Suspense fallback={<p>Loading.....</p>}>
<User />
</Suspense>
</div>
);
}
export const User = () => {
const data = useGetData('https://jsonplaceholder.typicode.com/todos/1');
return <div>{JSON.stringify(data)}</div>;
};
This is the custom hook useGetData I made:
// VERY BASIC IMPLEMENTATION OF A FETCHING LIBRARY BASED ON SUSPENSE
// This is the official basic promiseWrapper they implement in React Suspense Demo:
function wrapPromise(promise) {
let status = 'pending';
let result;
let suspender = promise.then(
(r) => {
status = 'success';
result = r;
},
(e) => {
status = 'error';
result = e;
}
);
return {
read() {
//console.log(status);
if (status === 'pending') {
throw suspender;
} else if (status === 'error') {
throw result;
} else if (status === 'success') {
return result;
}
},
};
}
/* Basic fetching function */
const fetcher = async (url) => {
try {
const res = await fetch(url);
const data = await res.json();
await delay(2000);
return data;
} catch (e) {
throw e;
}
};
/* Util to delay loading */
const delay = (d) => new Promise((r) => setTimeout(r, d));
/* HOOK that lets to start the fetch promise only on component mount */
/* It's based on "wrapPromise" utility, which is MANDATORY to return a Suspense consumable entity */
const useGetData = (url) => {
const [resource, setResource] = useState(null);
useEffect(() => {
const _resource = wrapPromise(fetcher(url));
setResource(_resource);
}, []);
return resource?.read();
};
Suspense
The working implementation is HERE
If you want to experiment how a Suspense-ready library works, you can check this example that makes use of the great state-manager valtio which is extremely light weight and easy to use, and suspense-ready. It let's you just pass a promise in your store, and when you try to access it in a component, the component will fallback if wrapped in <Suspense>.
Check it HERE
While you can wrap your own this it is better to use a library like Relay for fetching data or an alternative state manager such as Valtio or Proxily. With Proxily it would look like this:
import "./styles.css";
import { observable, observer, suspendable } from "proxily";
import React, { Suspense } from "react";
const fetcher = {
user: () =>
wait(2000)
.then(() => fetch("https://jsonplaceholder.typicode.com/todos/1"))
.then((response) => response.json())
};
suspendable(fetcher, (f) => f.user);
const state = observable(fetcher);
function App() {
return (
<div className="App">
<Suspense fallback={<span>Loading...</span>}>
<User />
</Suspense>
</div>
);
}
export const User = observer(() => {
return <div>{JSON.stringify(state.user())}</div>;
});
const wait = (t) => new Promise((r) => setTimeout(r, t));
export default observer(App);
You can see it in action here. This article discusses Suspense in the context of concurrent rendering and transitions, demonstrating how to make Suspense part of a transition so that when fetching new data, you can continue to display the old content rather that the fallback content until the new data is fetched.
I used axios in useEffect of my wrapper component and I sent the data as props to the other component "singleQuestionnaire", in singleQuestionnaire component, I destructured the data, in the first try, it works fine, but after reloading the page it doesn't work with an error : can not read property "map" of undefined
import React, { useEffect, useState } from "react";
import SingleQuestionnaire from "./SingleQuestionnaire";
import { fetchQuestions } from "../../../api/index";
const Questionnaires = ({ match }) => {
const [questions, setQuestions] = useState([]);
const pid = match.params.id;
const getQuestionnaire = async (pid) => {
try {
const { data } = await fetchQuestions(pid);
console.log(data.data, "action in component");
setQuestions(data.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getQuestionnaire(pid);
}, []);
console.log("all questions", questions);
return (
<div>
<SingleQuestionnaire questions={questions} setQuestions={setQuestions} />
</div>
);
};
export default Questionnaires;
and this is my singleQuestionnaire component:
import React, { useEffect, useState } from "react";
const SingleQuestionnaire = ({ questions, setQuestions }) => {
const [questionnaire, setQuestionnaire] = useState([]);
console.log(questions);
const { data } = questions;
console.log("data", data.farmInformationQuestionnaireData);
return <div>simple component</div>;
};
export default SingleQuestionnaire;
For the first time, in console I can see the data "data.data.farmInformationQuestionnaireData". It's an array but for the second time it's undefind.
because questions in SingleQuestionnaire is an empty array before we fetch
which causes an error here
const { data } = questions;
you can add a loading text because initially questions will be an empty array then it will be your res.data (assuming it's an object)
const SingleQuestionnaire = ({ questions, setQuestions }) => {
const [questionnaire, setQuestionnaire] = useState([]);
console.log(questions);
if(questions.length === 0 ) return <h1> Loading</h1>
const { data } = questions;
console.log("data", data.farmInformationQuestionnaireData);
return <div>simple component</div>;
};
it is happening because of the async API call. When you make an async call, the thread does not wait, it moves on and it starts executing other things.
Now your async call might be complete but your callback will not be executed until the stack is empty, that's just how javaScript works. I recommend you use some kind of loader gif or text
{questions ? <SingleQuestionnaire questions={questions} setQuestions={setQuestions} /> : <p>Loading...</p>}
When following this documentation https://reactjs.org/docs/faq-ajax.html I run the code and if i console.log() the items variable in the return statement. I get the following which causes issues because the first response is empty:
The code i used is the following:
import React, {useState, useEffect} from 'react';
import logo from './logo.svg';
import './App.css';
function App() {
const [error, setError] = useState(null);
const [isLoaded, setIsLoaded] = useState(false);
const [items, setItems] = useState([]);
useEffect(() => {
fetch("http://www.7timer.info/bin/api.pl?lon=113.17&lat=23.09&product=astro&output=json")
.then(res => res.json())
.then(
(result) => {
setIsLoaded(true);
setItems(result);
},
// Note: it's important to handle errors here
// instead of a catch() block so that we don't swallow
// exceptions from actual bugs in components.
(error) => {
setIsLoaded(true);
setError(error);
}
)
}, [])
if (error) {
return <div>Error: {error.message}</div>;
} else if (!isLoaded) {
return <div>Loading...</div>;
} else {
return (
<ul>
{console.log(items)}
</ul>
);
}
}
export default App;
Any help would be great.
It's not 'first response is empty' issue. First console.log runs before the async code executes, when async code executes and you change your state, component rerenders and reruns the console.log with newly updated items that are now filled with data fetched from api.
Perhaps you should read a bit more about react component lifecycles :)
You can use a conditional to run your code only if result is not empty to avoid crashing.
if (result) {
//your code
}
Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};
I don't think the getServerSideProps gets run, I'm just starting out and got no clue how to fix this, tried for a few hours but still getting undefined from the IndexPage console.log(props.data)
export default function IndexPage(props) {
console.log(props.data.copyright);
return (
<>
<div>{props.data.copyright}</div>
</>
)
}
export async function getServerSideProps() {
const res = await fetch(" https://api.nasa.gov/planetary/apod?api_key=DEMO_KEY");
const data = await res.json();
return { props: { data } };
}
Edited: the code works perfectly on local machine and vercel deployment, but not on codesandbox.io where I originally started /headache
You have to use a React Hook, and then call the function inside of that. You can then store the response in a state instead
Try something like this:
import fetch from "isomorphic-unfetch";
import React, {useEffect} from "react"
const IndexPage = props => {
console.log(props.data);
if(!props.data){
return <div>Waiting for data...</div>
}
return <div>main page</div>;
};
export async function getServerSideProps() {
const { API_URL } = process.env;
console.log("inside fetch");
const res = await fetch(`${API_URL}/movies`);
const data = await res.json();
return { props: { data } };
}
export default IndexPage;
Otherwise you can use the
getStaticPropsThen ou are ensured, that the data is there, when the component fetches..