What benefits does React suspense have? - javascript

I know React suspense is new and hasn't been officially released for production yet but I was wondering what the main benefits of it are/or using it would be?
I can't see anything it does as being "new" or replacing something that doesn't already exist?
I know it allows me to load stuff from top to bottom but I can do that anyway in react using my own components
can anyone give me some ideas as to what it may be good for?

it has several benefits to use:
it makes code splitting easy
(with new usecase) it makes data fetching so easy! Read this
it just suspends your component rendering and renders a fallback component until your component makes itself ready to show, by that you can create a skeleton flow for your async components so easily event with a simple UI ( imagine instead of created a loading login by useState Api or something else )
these were just simple benefits of Reacts Suspense/lazy api.

First of all, I would like to mention that Suspense is officially released since React 16.6. It is production-ready and it is not limited only to code-splitting. Any asynchronous code can be integrated with it.
As of the benefits, consider the following use-case:
We have several components that all use some asynchronous code inside them (like fetching remote resources)
We need to display a loading indicator until all components are finished doing their job
We need to display an appropriate error if some of the components have failed to do their duty
Old way
The good old way of doing this would be:
Create a wrapper component for showing loading indicator and error messages
Keep track of loading and error state inside of each component and inform the wrapper component of state changes
Does this all look like unnecessary, hard to change boilerplate? Yes, it does).
New way
React introduced the Suspense component and Error Boundaries to eliminate this boilerplate and to declaratively describe the desired behavior.
Check this out:
<Exception fallback="An error has occured">
<Suspense fallback="Loading...">
<OurComponent1 />
<OurComponent2 />
<OurComponent3 />
</Suspense>
</Exception>
Example
Suppose we want to fetch users' data from the remote resource.
const fetchUsers = async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await response.json();
console.log("Users data", users);
return users;
};
I will use makeSuspendableHook to integrate our asynchronous fetch within <Suspense> and Error boundary.
const useUsers = makeSuspendableHook(fetchUsers());
In our component, all we should care about is the actual data and its representation.
const Users = () => {
const users = useUsers();
return (
<div>
List fetched users:
<ul>
{users.map(({ name }) => (
<li>{name}</li>
))}
</ul>
</div>
);
}
Finally, I will use Exception as an Error Boundary implementation to stitch everything together.
export default () => (
<Exception fallback="An error has occurred">
<Suspense fallback="Waiting...">
<Users />
</Suspense>
</Exception>
);
Play with web example at codesandbox.io
Play with native example at snack.expo.io

Related

Will JSX conditional rendering out of an object code split?

Will conditional rendering out of an object code split and lazy load as expected? Here's a short example of what I'm talking about.
const Component1 = lazy(() => import('some path'));
const Component2 = lazy(() => import('some path'));
const Component3 = lazy(() => import('some path'));
render () {
const { selectionIndex } = this.state;
<Suspense fallback={<div>Loading...</div>}>
{{
one: <Component1 />,
two: <Component2 />,
three: <Component3 />,
}[selectionIndex]}
</Suspense>
}
I want to know whether all three components will load on render, or just the one selected by selectionIndex. I'm trying to use this to conditionally select something to display based on a menu set by state, but I don't want to load everything at once.
They will not get rendered all at once. You can experiment by yourself, put console.log inside components is an easy way to find out.
React for web consists of two libs, "react" and "react-dom". "react" is in charge of encapsulating your logic intention into declarative data structures, while "react-dom" consumes these data structures and handles the actual "rendering" part of job.
The JSX element creation syntax <Component {…props} /> translates to plain JS as an API call to React.createElement(Component, props). The return value of this API call is actually just a plain object of certain shape that roughly looks like:
{
type: Component,
props: props
}
This is the aforementioned "declarative data structure". You can inspect it in console.
As you can see, calling React.createElement just return such data structure, it will not directly call the .render() method or functional component’s function body. The data structure is submitted to "react-dom" lib to be eventually "rendered".
So your example code just create those data structures, but the related component will not be rendered.
seems like its conditionally loaded based on selectionIndex. all the other components are not loaded at once.
P.S.: if you ever feel like which will get load first, just put a console log in that component and debug easily
conditionally load demo link - if you open this, the components are being loaded initially based on selectionIndex value being "one".
I'm not going to go into too much technical detail, because I feel like #hackape already provided you with a great answer as to why, point of my answer is just to explain how (to check it)
In general, I'd recommend you to download download the React Developer Tools
chrome link
firefox link
and then you can check which components are being rendered if you open the components tab inside your developer console. Here's a sandboxed example, best way to find out is to test it yourself afterall :-)
As you can see in the developer tools (bottom right), only the currently set element is being rendered

Loading React hooks using dynamic imports?

I'm using a few third-party React hook libraries that aren't required for the initial render. E.g. react-use-gesture, react-spring, and react-hook-form. They all provide interactivity, which can wait until after the UI is rendered. I want to dynamically load these using Webpack's codesplitting (i.e. import()) after I render my component.
However, I can't stub out a React hook because it's essentially a conditional hook, which React doesn't support.
The 2 solutions that I can think of are:
Somehow extract the hook into a component and use composition
Force React to reconstruct a component after the hook loads
Both solutions seem hacky and it's likely that future engineers will mess it up. Are there better solutions for this?
As you say it, there are two ways to go about using lazy loaded hooks:
Load library in a Parent Component, conditionally render Component using library when available
Something along the lines of
let lib
const loadLib = () => {...}
const Component = () => {
const {...hooks} = lib
...
}
const Parent = () => {
const [loaded, setLoaded] = useState(false)
useEffect(() => loadComponent().then(() => setLoaded(true)), [])
return loaded && <Component/>
}
This method is indeed a little hacky and a lot of manual work for each library
Start loading a component using the hook, fail, reconstruct the component when the hook is loaded
This can be streamlined with the help of React.Suspense
<Suspense fallback={"Loading..."}>
<ComponentWithLazyHook/>
</Suspense>
Suspense works similar to Error Boundary like follows:
Component throws a Promise during rendering (via React.lazy or manually)
Suspense catches that Promise and renders Fallback
Promise resolves
Suspense re-renders the component
This way is likely to get more popular when Suspense for Data Fetching matures from experimental phase.
But for our purposes of loading a library once, and likely caching the result, a simple implementation of data fetching can do the trick
const cache = {}
const errorsCache = {}
// <Suspense> catches the thrown promise
// and rerenders children when promise resolves
export const useSuspense = (importPromise, cacheKey) => {
const cachedModule = cache[cacheKey]
// already loaded previously
if (cachedModule) return cachedModule
//prevents import() loop on failed imports
if (errorsCache[cacheKey]) throw errorsCache[cacheKey]
// gets caught by Suspense
throw importPromise
.then((mod) => (cache[cacheKey] = mod))
.catch((err) => {
errorsCache[cacheKey] = err
})
};
const SuspendedComp = () => {
const { useForm } = useSuspense(import("react-hook-form"), "react-hook-form")
const { register, handleSubmit, watch, errors } = useForm()
...
}
...
<Suspense fallback={null}>
<SuspendedComp/>
</Suspense>
You can see a sample implementation here.
Edit:
As I was writing the example in codesandbox, it completely escaped me that dependency resolution will behave differently than locally in webpack.
Webpack import() can't handle completely dynamic paths like import(importPath). It must have import('react-hook-form') somewhere statically, to create a chunk at build time.
So we must write import('react-hook-form') ourselves and also provide the importPath = 'react-hook-form' to use as a cache key.
I updated the codesanbox example to one that works with webpack, the old example, which won't work locally, can be found here
Have you considered stubbing the hooks? We used something similar to async load a large lib, but it was not a hook, so YMMV.
// init with stub
let _useDrag = () => undefined;
// load the actual implementation asynchronously
import('react-use-gesture').then(({useDrag}) => _useDrag = useDrag);
export asyncUseDrag = (cb) => _useDrag(cb)

Call api before first render in functional component in React.js

If I want to call API after the first rendering of component, I know we have useEffect hook to call the API method. (I am talking about functional components only. No class component).
Is there any way, I can call the API before my component renders the first time.
The reason for this question is, If some UI part is dependent on API, I do not want to show any incomplete information to the user on the first render also, which will be changed once I get the data from API.
This seems to be a bad experience with UI.
Edit: I got a couple of advice to use useLayoutEffect or any consumable flag to check if it is rendered or not. I have checked useLayoutEffect does not work, and by using the consumable flag, we are increasing the complexity only.
Do we have any better way for this?
I think useLayoutEffect can be used for something like this, by passing in an empty array as second argument. useLayoutEffect(() => {...}, []);
Updates scheduled inside useLayoutEffect will be flushed synchronously, before the browser has a chance to paint.
Although you can always fetch the data in the parent component and pass it as props. Or - if you don't mind it being an experimental feature for now - React Suspense is trying to solve this exact problem.
There are no correct ways to make API call before component rendered from the same component.
You may preferred make API call in parent component and render presentation component when and only when have consumable data.
Another workaround for such case is keep consumable flag inside component, make request inside useEffect, render nothing or some kind loader and render something only when request completed with success.
on calling api it is not responding exact on its first render but giving exact response when it's being hit second time
You can have a spinner or loading component be rendered first conditionally (isLoading for example):
if(isLoading) return <Spinner />
and have the api call set (isLoading) to false on status 200 for example.
Just came across something, which may help someone in future. So we can use some library but the specific one I would mention here is React Query
React query does exactly what we are trying to achieve, the hooks like useQuery fetch data as soon as rendering starts so you don’t have to wait until react loads the entire component as follows
// with react query
const { status, data, error, isFetching } = useQuery(
['data'],
async () => {
const data = await (
await fetch(`${API_BASE_URL}/data`)
).json()
return data
}
)
// without react query
useEffect(() => {
try {
setLoading(true)(async () => {
const data = await (await fetch(`${API_BASE_URL}/data`)).json();
setData(data);
})();
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}, []);
Here is the article link if you want to read

React router not reloading Component when changing url params

I know that it's not a default behaviour / feature of react-router to help us reload easily the current component but I really need this in my application.
My application deals with products. I have a product list that I can load, and when I click on an item, it displays the concerned product details.
On that page, I have related product links that load the same component, but with another product details, located at
<Route path="/products/:id/details" component={ProductDetail} />
I m fetching data in my componentWillMount, and it seems that if I only change the URL, a new component is NOT mounted, and so, I m always having my old data displayed, without fetching anything.
As a beginner using React, I'm looking for some help, or some tricks to reload the component concerned by that page. I mean being able to reload the ProductDetail with the good product.
I tried to look around with componentWillUpdate (a method in which I can see that the router URI changes :D) but I can't setState inside of it to make my component reload (it doesn't seem to be a good practice at all)
Any idea how can I make this work ?
EDIT : According to the first answer, I have to use onEnter. I m now stuck with the way of passing state/props to the concerned component :
const onEnterMethod = () => {
return fetch(URL)
.then(res => res.json())
.then(cmp => {
if (cmp.length === 1) {
// How to pass state / props to the next component ?
}
});
};
The way to handle it depends if you are using flux, redux or however you want to manage your actions. On top of it I would try to make use of onChange property of Route component (check React router docs):
<Route path="/products/:id/details" component={ProductDetail} onChange={someMethod} />
And then in the someMethod create the action if you are using redux or however is done in flux.
The redux would be:
<Route path="/products/:id/details" component={ProductDetail} onEnter={onEnterHandler(store)} />
And the onEnterHandler with the redux store:
function onEnterHandler(store) {
return (nextState, replace) => {
store.dispatch({
type: "CHANGEPRODUCT",
payload: nextState.params.id
})
};
}
And then in your ProductDetail component you would print the new information (It would require a bit more of learning in redux and redux-sagas libraries to complete that part).
Keep in mind that React is just the view part, trying to solve all those problems using only react is not only not recommended but also would mess up your code.

Sharing global/singleton data in react app

I'm rewriting a small app to try and better understand React. I'm trying to determine the "correct"/most efficient method of sharing "singleton" data - for example, a user who's been properly authenticated upon login.
Right now the parent "application" component has a user property in its state, which I pass to child components as a prop:
<Toolbar user={this.state.user} />
<RouteHandler user={this.state.user}/>
(I'm using react-router). This works, and in read-only cases like this, isn't terrible. However, my actual login form component (which is a route, and would be inside RouteHandler), needs some way to "set" the new user data, so I also need to pass in some callback:
<RouteHandler onAuthenticated={this.setUser} user={this.state.user}/>
Not a big problem, except for the fact that now this method is available to every "route" handled by RouteHandler.
I've been reading up and it seems like the only alternative is an EventEmitter or Dispatch-style system.
Is there a better way I'm missing? Is an event emitter/dispatcher system worth using when there's really only one or two uses in an app this small?
React Context provides a way to pass data through the component tree without having to pass props down manually at every level. With context, every component nested under a Provider has access to the data, but you need to explicitly read the value.
I recommend using React Hooks with useContext. One way to do this would be to set the value of the context to be an object with setter and getter functions.
import React, { useState, useContext } from "react"
export const UserContext = React.createContext({}); //Initialise
//Wrapper with getter and setter
const App = () => {
const [user, setUser] = useState();
const value = {user, setUser}
return (
<div>
<UserContext.Provider value={value}>
<RouteHandler/>
<AnotherComponent/>
</UserContext>
<ComponentWithoutAccessToUserContext/>
</div>
)
}
const RouteHandler = (props)=> {
const { user, setUser } = useContext(UserContext)
// This component now has access to read 'user' and modify it with 'setUser'
}
const AnotherComponent = () => {
return (<div>Component which could be get access to the UserContext</div>)
}
For singleton - you can just create separate module for user service and import it into module where you define components that need it it.
Other quite similar, but more powerful option, is to use DI container - define your react components as a services in DI container, with dependencies to other services like one for user data. This would be more suitable for universal(isomorphic) app - because, you will be able to easily replace dependencies with specific implementations, or for case when you need to create separate instances for separate scopes(like for user sessions server-side).
Also if using this approach, I would recommend to separate pure react components from logic - you can create separate pure component that receives all data, and callbacks as a props, and than create HoC component in DI container that will wrap it and will pass needed data and callbacks.
If you need DI container - there is a plenty of them, but I will recommend to look at angular 2 di container, or if you would like something simpler - below I referenced my project, it has very simple but yet powerful DI inspired by angular 2 DI(it is easy to pull from that project - just one file + test)).
About notifying components about changes, and organising async logic - you still will need something like EventEmitter to notify components about changes, and you will need to write life cycle callbacks for components to subscribe/unsubscribe from updates… You can do this by hand or creating mixin or HoC to shorten that.
But from my perspective, there is better approach - try reactive programming, and RxJS in particular. It plays very well with react.
If you are interested about options connecting Rx with React - take a look at gist https://gist.github.com/zxbodya/20c63681d45a049df3fc, also it can be helpful about implementing HoC component with subscription to EventEmitter mentioned above.
I have a project that is intended for creating isomorphic(rendered server side, and than same html reused client side) widgets with react.
It has DI container to pass dependencies, and it uses RxJS to manage async logic:
https://github.com/zxbodya/reactive-widgets
One way is to subscribe to an Observable emitted from your data model.
Router.run(routes, Handler =>
Model.subject.subscribe(appState =>
React.render(
<Handler {...appState}/>,
document.getElementById('app')
)
)
);
...appState being the data coming from observable (in this case model), making these your props so you can then feed them to the app like below
<RouteHandler {...this.props} />
and any child component can pick them up with this.props
the answer is more complex that this but if you look at RxJS+React you will get a full working examples of simple data flows

Categories

Resources