React 18 Suspense Fallback not working on Data Fetching? - javascript

I am basically fetching a bunch of user data and displaying a list of their usernames on page load. I am using the suspense with the intention of displaying the "Loading..." text while the userList component is being processed, but for some reason it does'nt display the loading text mentioned inside the fallback. I am using the latest react 18 version.
import React, { useState, useEffect, Suspense } from "react";
function UsersList() {
// Fetches and returns List of users
const [users, setUsers] = useState([]);
useEffect(() => {
try {
fetch("https://jsonplaceholder.typicode.com/users")
.then((response) => response.json())
.then((data) => setUsers(data));
} catch (error) {
// handle the error here
}
}, []);
return (
<div>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
function App() {
return (
<div className="App">
<h2>Users List</h2>
<Suspense fallback={<h1> Loading...</h1>}>
<h4> Below are the user detailed fetched : </h4>
<UsersList />
</Suspense>
</div>
);
}
export default App;
I tried using throttling to reduce network speed on chrome dev tool, but still the loading text is not being displayed.

How does Suspense is suppose to know that you're in the process of fetching the data?
From the docs:
Only Suspense-enabled data sources will activate the Suspense component.
They include:
Data fetching with Suspense-enabled frameworks like Relay and Next.js
Lazy-loading component code with lazy
Suspense does not detect when data is fetched inside an Effect or event handler.
Suspense-enabled data fetching without the use of an opinionated framework is not yet supported. The requirements for implementing a Suspense-enabled data source are unstable and undocumented. An official API for integrating data sources with Suspense will be released in a future version of React.
Tl;DR: You can't easily use it by yourself yet.

Related

calling setState only once inside of useEffect--is there a better method?

In my react app I use the following pattern quite a bit:
export default function Profile() {
const [username, setUsername] = React.useState<string | null>(null);
React.useEffect(()=>{
fetch(`/api/userprofiles?username=myuser`)
.then(res=>res.json())
.then(data => setUsername(data.username))
},[])
return(
<div>
{username}'s profile
</div>
)
}
When the page loads, some user data is fetched from the server, and then the page updates with that user data.
One thing I notice is that I only really need to call setUsername() once on load, which makes using state seem kinda excessive. I can't shake the feeling that there must be a better way to do this in react, but I couldn't really find an alternative when googling. Is there a more efficient way to do this without using state? Or is this the generally agreed upon way to load data when it only needs to be done once on page load
Without using any external libraries, no - that is the way to do it.
It would be possible to remove the state in Profile and have it render the username from a prop, but that would require adding the state into the parent component and making the asynchronous request there. State will be needed somewhere in the app pertaining to this data.
The logic can be abstracted behind a custom hook. For example, one library has useFetch where you could do
export default function Profile() {
const { data, error } = useFetch('/api/userprofiles?username=myuser');
// you can check for errors if desired...
return(
<div>
{data.username}'s profile
</div>
)
}
Now the state is inside useFetch instead of in your components, but it's still there.

REACT JS - Passing values from one component to another

I have a NavBar.js component thats holds login info on the logged in user. When the user is logged in it says "Welcome" along with the user details. I want to implement the same function in ProductList.js component so when a user posts a blog, it says "Posted By: " along with the users log in details. How would I pass the details form NavBar.js to ProductList.js ?
import React, { useState, useEffect } from 'react';
import { NavLink } from 'react-router-dom';
const NavBar = (props) => {
const providers = ['twitter', 'github', 'aad'];
const redirect = window.location.pathname;
const [userInfo, setUserInfo] = useState();
useEffect(() => {
(async () => {
setUserInfo(await getUserInfo());
})();
}, []);
async function getUserInfo() {
try {
const response = await fetch('/.auth/me');
const payload = await response.json();
const { clientPrincipal } = payload;
return clientPrincipal;
} catch (error) {
console.error('No profile could be found');
return undefined;
}
}
return (
<div className="column is-2">
<nav className="menu">
<p className="menu-label">Menu</p>
<ul className="menu-list">
<NavLink to="/products" activeClassName="active-link">
Recipes
</NavLink>
<NavLink to="/about" activeClassName="active-link">
Help
</NavLink>
</ul>
{props.children}
</nav>
<nav className="menu auth">
<p className="menu-label">LOGIN</p>
<div className="menu-list auth">
{!userInfo &&
providers.map((provider) => (
<a key={provider} href={`/.auth/login/${provider}?post_login_redirect_uri=${redirect}`}>
{provider}
</a>
))}
{userInfo && <a href={`/.auth/logout?post_logout_redirect_uri=${redirect}`}>Logout</a>}
</div>
</nav>
{userInfo && (
<div>
<div className="user">
<p>Welcome</p>
<p>{userInfo && userInfo.userDetails}</p>
<p>{userInfo && userInfo.identityProvider}</p>
</div>
</div>
)}
</div>
);
};
export default NavBar;
This is a snippet from ProductsList.js, where I want the user details data to be passed to:
<footer className="card-footer ">
<ButtonFooter
className="cancel-button"
iconClasses="fas fa-undo"
onClick={handleCancelProduct}
label="Cancel"
/>
<ButtonFooter
className="save-button"
iconClasses="fas fa-save"
onClick={handleSave}
label="Save"
/> Posted By: {}
</footer>
You have to use a global state manager like redux , Recoil , context-api etc. If the state receiver component is not a child component of state sender component, you can't use props. You may check Redux official documentation.
You can probably force your current set up to work using an event emitter, where your ProductList sends out an event requesting the user info, and the NavBar is listening for those events, and responds with the appropriate data.
I don't recommend this, though, with your current set up.
I recommend lifing your user info logic out of the NavBar and into app state that sits above both your NavBar and your ProductList in the component tree. The idea is that you'll have a store of app state data that sits near the root level of your app, to hold things that multiple parts of your app may want access to at different moments. In this case, your NavBar and your ProductList both care about user data.
This also allows the NavBar component focus on generating HTML rather than data fetching and storing. Separating these concerns assists greatly with your app growing over time (maybe you create another component down the road that also wants to know about User info), and then also things like testing so that you don't have to have a mock API in place to see if your NavBar is displaying the data it expects correctly.
What I'm suggesting will pay off in the end, but it does increase the complexity of what you're doing. You'll have to rearchitect a few pieces of your app to put a centralized data store in place. And create ways to add data to that store (a user logs in) and get data out of the store (components want to display user info).
There are several popular ways to handle this sort of app state store.
Redux taught me a lot about how to organize data flows and has a library react-redux to integrate directly with your React components
React has Context Provider and useContext hook, and even has a useReducer hook that allows you to re-create Redux using native React tools
RxJS I have not used personally, but it offers some benefits that the others do not.
There are many more. Google "React state libraries" to explore your options
I know this doesn't directly answer your question, but I hope it helps a bit nonetheless.

React app 'crashes' due to fetch/axios inability to correctly pass error to catch block

I have a small React app that fetches some resources with axios through UseEffect() hook. The app itself works fine in debug. However, when I try to test out the build version of my app, it 'crashes', that is, it doesn't render anything aside from setting body background color.
When in debug mode, the app shows 0 errors or warnings, and the only leads the build has is TypeError's for built-in type-specific methods like .map(), .filter() and .toLowerCase() all which, conveniently, are present in components that fetch data with axios. After some testing I came to conclusion that the cause of the 'crash' was axios (and fetch()) that, for a reason unknown to me, doesn't pass the error upon unsuccessful fetch to the .catch() block and tries instead to execute .then() with undefined data, which, in turn, don't have methods like .map() or .toLowerCase(), cause TypeError's and 'crash' the whole app.
I tried to rewrite all axios containing components with fetch(), but the result is the same. The closest thing I have found on my own was this answer for React Native (and not plain React) that advises to change console.error() (which crashes the app) to console.log(), but it's hardly applicable in my case since I don't use console.error() at all.
Below you can find a sample component that fetches data with axios:
import axios from 'axios';
import { useState, useEffect } from 'react';
const Salute = () => {
const [user, setUser] = useState('john doe');
useEffect(() => {
axios
.get('/api/user?id=1')
.then(({ data: { fullName } }) => setUser(fullName.toLowerCase()))
.catch((err) => console.log(err));
}, []);
return (
<div className='salute'>
<h1>
hi, it's <span id='username'>{user}</span>
</h1>
</div>
);
};
export default Salute;
The root cause of my problem turned out to be faulty API which sends error message with a 200 status code, which in turn was interpreted by axios/fetch() as a normal response.
The key takeaway here (at least for me) is to implement at least some minimal data validation in fetching logic so as to prevent the cases like these. In my case, I did the following (until the API I use gets fixed):
import axios from 'axios';
import { useState, useEffect } from 'react';
const Salute = () => {
const [user, setUser] = useState('john doe');
useEffect(() => {
axios
.get('/api/user?id=1')
.then(({ data }) => {
if (typeof data.fullName === 'string') {
setUser(data.fullName.toLowerCase());
} else {
console.log('[AXIOS GET] Wrong data type: ' + typeof data.fullName);
}
})
.catch((err) => console.log('[AXIOS GET]', err));
}, []);
return (
<div className='salute'>
<h1>
hi, it's <span id='username'>{user}</span>
</h1>
</div>
);
};
export default Salute;

Best practice for Next.js data fetching inside a component

I have a menu component that appears globally. What is the best practice for getting data into that component?
I'm trying to take advantage of static generation that Next.js offers but all data fetching guidance from the Next.js team relates to pages. getStaticProps and getStaticPaths seem to pertain to page generation, not data for components. Is their SWR package the right answer, or Apollo Client?
Typically in hooks-based React, I'd just put my data call into useEffect. I'm not sure how to reason this out being that everything is rendered at build time with Next.
This is such a tricky problem, I think we need to lay out some background before a solution comes into focus. I'm focusing in the React.js world but a lot of this would apply to Vue/Nuxt I'd imagine.
Background / Static Generation Benefits:
Gatsby and Next are focused on generating static pages, which vastly improves performance and SEO in React.js sites. There is a lot of technical overhead to both platforms beyond this simple insight but let's start with this idea of a digital machine pumping out fancy HTML pages for the browser.
Data Fetching for Pages
In the case of Next.js (as of v9.5), their data fetching mechanism getStaticProps does most of the heavy lifting for you but it's sandboxed to the /pages/ directory. The idea is that it does the data fetching for you and tells the Next.js page generator in Node about it during build time (instead of doing it component-side in a useEffect hook - or componentDidMount). Gatsby does much the same with their gatsby-node.js file, which orchestrates the data fetching for page building in concert with a Node server.
What about Global Components that need data?
You can use both Gatsby and Next to produce any kind of website but a huge use case are CMS-driven websites, because so much of that content is static. These tools are an ideal fit to that use case.
In typical CMS sites, you will have elements that are global - header, footer, search, menu, etc. This is where static generation faces a big challenge: how do I get data into dynamic global components at build time? The answer to this question is... you don't. And if you think about this for a minute it makes sense. If you had a 10K page site, would you want to trigger a site-wide rebuild if someone adds a new nav item to a menu?
Data Fetching for Global Components
So how do we get around this? The best answer I have is apollo-client and to do the fetch client side. This helps us for a number of reasons:
For small size queries, the performance impact is negligible.
If we need to rebuild pages for changes at the CMS layer, this slides by Next/Gatsby's detection mechanisms, so we can make global changes without triggering gigantic site-wide rebuilds.
So what does this actually look like? At the component level, it looks just like a regular Apollo-enhanced component would. I usually use styled-components but I tried to strip that out so you can could better see what's going on.
import React from 'react'
import { useQuery, gql } from '#apollo/client'
import close from '../public/close.svg'
/**
* <NavMenu>
*
* Just a typical menu you might see on a CMS-driven site. It takes in a couple of props to move state around.
*
* #param { boolean } menuState - lifted state true/false toggle for menu opening/closing
* #param { function } handleMenu - lifted state changer for menuState, handles click event
*/
const NAV_MENU_DATA = gql`
query NavMenu($uid: String!, $lang: String!) {
nav_menu(uid: $uid, lang: $lang) {
main_menu_items {
item {
... on Landing_page {
title
_linkType
_meta {
uid
id
}
}
}
}
}
}
`
const NavMenu = ({ menuState, handleMenu }) => {
// Query for nav menu from Apollo, this is where you pass in your GraphQL variables
const { loading, error, data } = useQuery(NAV_MENU_DATA, {
variables: {
"uid": "nav-menu",
"lang": "en-us"
}
})
if (loading) return `<p>Loading...</p>`;
if (error) return `Error! ${error}`;
// Destructuring the data object
const { nav_menu: { main_menu_items } } = data
// `menuState` checks just make sure out menu was turned on
if (data) return(
<>
<section menuState={ menuState }>
<div>
{ menuState === true && (
<div>Explore</div>
)}
<div onClick={ handleMenu }>
{ menuState === true && (
<svg src={ close } />
)}
</div>
</div>
{ menuState === true && (
<ul>
{ data.map( (item) => {
return (
<li link={ item }>
{ item.title }
</li>
)
})}
</ul>
)}
</section>
</>
)
}
export default NavMenu
Set Up for Next to Use Apollo
This is actually really well documented by the Next.js team, which makes me feel like I'm not totally hacking the way this tool is supposed to work. You can find great examples of using Apollo in their repo.
Steps to get Apollo into a Next app:
Make a custom useApollo hook that sets up the connection to your data source (I put mine in /lib/apollo/apolloClient.js within Next's hierarchy but I'm sure it could go elsewhere).
import { useMemo } from 'react'
import { ApolloClient, InMemoryCache, SchemaLink, HttpLink } from '#apollo/client'
let apolloClient
// This is mostly from next.js official repo on how best to integrate Next and Apollo
function createIsomorphLink() {
// only if you need to do auth
if (typeof window === 'undefined') {
// return new SchemaLink({ schema })
return null
}
// This sets up the connection to your endpoint, will vary widely.
else {
return new HttpLink({
uri: `https://yourendpoint.io/graphql`
})
}
}
// Function that leverages ApolloClient setup, you could just use this and skip the above function if you aren't doing any authenticated routes
function createApolloClient() {
return new ApolloClient({
ssrMode: typeof window === 'undefined',
link: createIsomorphLink(),
cache: new InMemoryCache(),
})
}
export function initializeApollo(initialState = null) {
const _apolloClient = apolloClient ?? createApolloClient()
// If your page has Next.js data fetching methods that use Apollo Client, the initial state
// gets hydrated here
if (initialState) {
// Get existing cache, loaded during client side data fetching
const existingCache = _apolloClient.extract()
// Restore the cache using the data passed from getStaticProps/getServerSideProps
// combined with the existing cached data
_apolloClient.cache.restore({ ...existingCache, ...initialState })
}
// For SSG and SSR always create a new Apollo Client
if (typeof window === 'undefined') return _apolloClient
// Create the Apollo Client once in the client
if (!apolloClient) apolloClient = _apolloClient
return _apolloClient
}
// This is goal, now we have a custom hook we can use to set up Apollo across our app. Make sure to export this!
export function useApollo(initialState) {
const store = useMemo(() => initializeApollo(initialState), [initialState])
return store
}
Modify _app.js in the /pages/ directory of Next. This is basically the wrapper that goes around every page in Next. We're going to add the Apollo provider to this, and now we can globally access Apollo from any component.
import { ApolloProvider } from '#apollo/react-hooks'
import { useApollo } from '../lib/apollo/apolloClient'
/**
* <MyApp>
*
* This is an override of the default _app.js setup Next.js uses
*
* <ApolloProvider> gives components global access to GraphQL data fetched in the components (like menus)
*
*/
const MyApp = ({ Component, pageProps }) => {
// Instantiates Apollo client, reads Next.js props and initialized Apollo with them - this caches data into Apollo.
const apolloClient = useApollo(pageProps.initialApolloState)
return (
<ApolloProvider client={ apolloClient }>
<Component {...pageProps} />
</ApolloProvider>
)
}
export default MyApp
And now you can get dynamic data inside of your components using Apollo! So easy right ;) HA!
For global data fetching in NextJS, I use react-query and there is no need for a global state because it lets you to cache the data. Let's say you have a blog with categories and you want to put the category names in the navbar as a dropdown menu. In this case you can call the API to fetch the data with react-query from the navbar component and cache it. The navbar data will be available for all pages.

How to handle asynchronous fetch from Database with Gatsby or React functional component

I am building a Gatsby app, that's mainly written in React. I have a LoggedIn component where I would grab all books that the user has borrowed and display the status on the website. I use Firebase. I hope that within the LoggedIn component, I can fetch the books. But I am not sure how to wait for the fetch method is done. When I use async/await, it just broke because then my functional component would return a Promise instead of a JSX:ELEMENT type. How can I handle this problem?
import React, { useState } from 'react'
import {fetchUserBook} from "../../firebase/firebaseService"
const LoggedIn = ({user}) => { //if I put async before user,
//my LoggedIn component will return a promise, not a JSX component, which will break my code.
const[books,setBooks] = useState([])
fetchUserRestaurant(user.email).then((info) => setBooks(info))
const renderloggedIn = () =>{
return (
<>
<h1>Welcome, {user.email}.</h1> // I hope that I can pass the "books" props here so that I can render it.
// But usually the return statement is invoked before my fetchUserRestaurant method finishes.
</>
)
}
return(
renderloggedIn()
)
}
export default LoggedIn
``
You just need to put your async fetch function inside a useEffect hook because it will be triggered once the DOM tree is loaded. Just add:
useEffect(()=>{
fetchUserRestaurant(user.email).then((info) => setBooks(info))
}, [])
Adding an empty array (deps), will make it will work as a componentDidMount(), since, in a stateless component like yours, you can't use a componentDidMount() lifecycle, you have to use hooks. This will cause a blink content until your request populates your useState hook and it is displayed. You can add a loader or whatever you like if you want to bypass it anyway.
The rest of the code seems correct.

Categories