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.
Related
Whenever I visit a page it should automatically fetch the API
import React from 'react'
const Component = () => {
fetch("api url").then((res) => console.log(res))
return (
<div>comp</div>
)
}
export default Component
It is very simple using react hook use effect please learn basics of useffect hook on react docs or any youtube tutorial and as for the answer
import React, { useEffect } from 'react'
const comp = () => {
useEffect(() => {
fetch("api url").then((res)=>console.log(res))
}, [])
return (
<div>comp</div>
)
}
export default comp
here empty dependency means every time page loads only once
use the useEffect for this.
The useEffect method will execute the passed callback on the mount of the component and on every time one of the dependency array parameters is changed. therefore:
const Comp = () => {
useEffect(() => {
fetch("api url").then((res)=>console.log(res))
}, []);
return (
<div>comp</div>
)
}
Will make the callback to fire only once (because the empty dependency array) on the component mount.
You should use the useEffect Hook in your principal component like app.js
import React, {useEffect} from 'react'
useEffect(() => {
fetch("api url").then((res)=>console.log(res))
}, []);
Be careful, this manipulation can consume a lot of resources (a lot of data to fetch etc.)
Thery
import React, { useState, useEffect } from 'react'
const Comp = () => {
const [ data, setData ] = useState([]);
const getData = async () => {
const res = await fetch("api url");
const data = await res.json();
setData(data)
}
useEffect(()=>{ getData() },[]);
return (
<>
<div>comp</div>
// dispaly your data here from data state
</>
)
}
export default Comp;
Fetch and use data with useState
const initialValue = {};
const comp = () => {
const [data, setData] = useState(initialValue);
useEffect(() => {
let ignore = false;
const fetchData = async () => {
const res = fetch("api url");
if (ignore) { return; }
setData(res.json())
return () => {
ignore = true;
}
}
, [])
return (
<div>comp {data.prop}</div>
)
}
More on working with state
More about useEffect life cycle
Hope it helps
You don't need to use the API function like this, it will be called continuously, you need to use useEffect hook, when your component reloads useEffect will be called, and you can learn about the useEffect dependency here,
import React, { useEffect, useState } from 'react'
const comp = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch("api url").then((res)=> {
console.log(res)
setData(res)
} )
}, [])
return (
// use data state to show the data here
<div>comp</div>
)
}
export default comp;
I am beginner in JS and React.
I have a problem:
import React from "react";
import JsonApi from "../../services/jsonApi";
const UserPage = () => {
const jsonApi = new JsonApi(); //it is my class which has methods
//to manage with data(get,post,etc);
const user = jsonApi.getUser(); //returns promise,but i need an object with data!
//promise has such view:
//[[Prototype]]: Promise
//[[PromiseState]]: "fulfilled"
//[[PromiseResult]]: Object !!!!i need this data!!!!
console.log(user); //Promise.
/* i know that a i can do so:
user.then((data) => console.log(data));
but,using this way,i can only log!But i need an object with data!
*/
return (
<div className="app">
<h1>{user.name}</h1>
<p>Here are info about users!</p>
</div>
);
};
export default UserPage;
I understand that i need to use await before const user = jsonApi.getUser();
but we can do that only inside async functions.
So,i tried to do that: const UserPage = async () => { }
but i had a mistake:
In order to perform side effects in react you should consider using useEffect hook. After the effect you need to store the data retrieved in react state by using the useState hook. In the end your code would look like below:
import React, { useState, useEffect } from "react";
import JsonApi from "../../services/jsonApi";
const UserPage = () => {
const [user, setUser] = useState(null);
useEffect(() => {
const jsonApi = new JsonApi();
jsonApi.getUser().then((user) => {
setUser(user);
});
}, []);
if (!user) return null;
return (
<div className="app">
<h1>{user.name}</h1>
<p>Here are info about users!</p>
</div>
);
};
export default UserPage;
Keep in mind that the user is not populated until you async getUser resolves, so you have to handle the case where user data are not yet present, either by rendering nothing (null) or by showing some loading state in between.
I have my state and I want to display the component if the value is true but in the console I receive the error message Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state my code
import React, { useState} from "react";
import { useToasts } from "react-toast-notifications";
const Index = () => {
const [test, setTest]= useState(true);
const { addToast } = useToasts();
function RenderToast() {
return (
<div>
{ addToast('message') }
</div>
)}
return (
<div>
{test && <RenderToast /> }
</div>
)
}
You cannot set state during a render. And I'm guessing that addToast internally sets some state.
And looking at the docs for that library, you don't explicitly render the toasts. You just call addToast and then the <ToastProvider/> farther up in the tree shows them.
So to make this simple example works where a toast is shown on mount, you should use an effect to add the toast after the first render, and make sure your component is wrapped by <ToastProvider>
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
addToast('message')
}, [])
return <>Some Content here</>
}
// Example app that includes the toast provider
const MyApp = () => {
<ToastProvider>
<Index />
</ToastProvider>
}
how i can display the toast based on a variable for exemple display toast after receive error on backend?
You simply call addToast where you are handling your server communication.
For example:
const Index = () => {
const { addToast } = useToasts();
useEffect(() => {
fetchDataFromApi()
.then(data => ...)
.catch(error => addToast(`error: ${error}`))
}, [])
//...
}
I'm working on a react project where I have created an API and that I want to use for my react project, I'm dealing with react hooks and not able to call API correctly. I have tried couple of things, but I keep getting one or other kind of error.
Latest method I have tried is giving me this error
Line 8:20: React Hook "useFetch" is called in function "filterList" which is neither a React function component or a custom React Hook function
Code for filterList.js look like this
/* to filter the products based on the categories */
import useFetch from './apiService'
export default function filterList(arr, method, courses) {
const URL = "http://localhost:8000/api/getCourses";
const result = useFetch(URL, {});
courses = result;
console.log(courses, 'Inside filterList')
if(method == null) return courses;
else {
return courses.filter(course => {
const categoryArray = course.category.split(" ");
if(arr.length > 0) {
if(categoryArray.some(r => arr.indexOf(r) >= 0)) {
return course;
}
}
else {
return courses;
}
return courses;
})
}
}
and code for apiService.js where I have created useFetch function look like this
import { useEffect, useState } from 'react';
// to fetch the data from api
const useFetch = (url, defaultData) => {
const [data, updateData] = useState(defaultData);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const json = await response.json();
updateData(json);
}
fetchData();
}, [url]);
return data;
};
export default useFetch;
Please let me know what is the thing that I'm doing wrong and what should be correct method to do this.
I'm creating my first MERN stack application, and trying to implement a simple API that calls my express server from my React front-end components. I have the API working on the back end, and it is sending the data correctly through fetch(), but I'm having trouble resolving the promise from fetch() in my React component, with the call not stopping firing. My code looks as follows (assuming as of right now all API calls return a dummy format like { title: 'foo', ... }:
import React, { useState } from 'react';
import 'core-js/stable';
import 'regenerator-runtime/runtime';
const getApiData = async (route) => {
try {
let apiData = await fetch(route);
let apiDataJson = await apiData.json();
return apiDataJson;
} catch (err) {
throw new Error('Error on fetch', {
error: err
})
}
}
var retrieve_data = async (route, setterCallback) => {
await getApiData(`/api/${route}`).then((data) => {
console.log('Data retrieved from API')
setterCallback(<div>{data.title}</div>)
}).catch(() => {
setterCallback(<div>ERROR</div>)
})
}
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
let data = retrieve_data('myEndpoint', setinnerDiv);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}
When I compile the above the component successfully renders (i.e. <MyComponent /> looks like:
<div>
<h1>Data Retrieved in MyComponent:</h1>
<div>foo</div>
</div>
However, then then block keeps executing (i.e. the 'Data retrieved from API' logs to the console hundreds of times/second until I close the application. How can I stop this from executing once it has set the component? Thanks!
You need to useEffect to stop the component from re-rendering. Try something like this.
const MyComponent = () => {
const [innerDiv, setinnerDiv] = useState(0);
useEffect(() => {
retrieve_data('myEndpoint', setinnerDiv);
}, []);
return(
<div>
<h1>Data Retrieved in MyComponent:</h1>
{innerDiv}
</div>
);
}