Web apps are focused on two primary activities: loading data and displaying it effectively. While React does a great job of displaying data with the help of native CSS, CSS in JS, and other styling alternatives, it falls short when it comes to the loading part.
A combination of fetching with proper lifecycle events might be a good solution for a complete client-side application, but when things get on the server, it’s not enough. This is where a third-party library known as React Frontload comes in. React Frontload is an effective tool for solving the issue of cross-environment data fetching and has a seamless interface that allows you to load your data inline.
Is that all? Let’s find out!
React Frontload is a library for React-based applications that helps to fetch data inline in both server and client environments. It offers several features that make it super simple to integrate and use. It offers a Hook for loading data, which returns the data in a stateful object, as well as a frontloadMeta
object for keeping track of the status of the network request.
An important point to focus on here is that the latest version of Frontload comes with an out-of-the-box state management solution, so you as a developer do not need to worry about manually handing over data to a state container such as Redux or MobX.
With over 6k weekly downloads via npm, React Frontload has started to attract attention due to its simple set-up and developer-friendly interface. Before we dive into how to get it running, let’s take a look at the original problem in detail and how Frontload tackles it.
As mentioned earlier, React does not offer an out-of-the-box solution to handle data fetching. The fetch API in JavaScript is capable enough of hitting external API endpoints and receiving data. But when it comes to React applications, a lot more has to be done. Once data is received, the React DOM needs to be informed about the result of the request.
If the request fails, the scenario has to be properly handled. And by the time you figure out a working solution for these cases, server-side rendered applications kick in, rendering your fetch + useEffect solution pointless.
Server-side rendering has its issues. While the asynchronous way of fetching data in parallel with rendering UI has been a great fit with developers, server-side rendering forces users to wait before all API calls are done and all data has loaded before rendering the layout and sending it down to the browser client.
This is why the conventional methods of asynchronously fetching data inside of components becomes irrelevant.
One might argue that a good way to solve this is to load data globally in server-side rendered apps and pass it down to various components. This also works on the client-side, as the root component can still handle fetching data and pass it down to its children via props.
But this is not a practical solution. This approach can prove to be effective in constructs like a search results page or a user profile page, but it increases complexity unnecessarily in cases such as a user’s feed or social media post.
React Frontload can help to solve this issue. The library offers a component-centric approach to implementing data fetching with in-built compatibility with SSR apps as well. This means that you do not need to implement different solutions for the two scenarios, and you are not forced to compromise on your application’s architecture either.
React Frontload builds on the component-specific data fetching method and adapts server-side rendering of the application to handle synchronous requests. This means that you can access the Frontload API via a React Hook and still get it working in SSR since your web server is instructed to work asynchronously just like a standard React client.
React Frontload helps your server load data asynchronously and renders the page. It returns data for the client-side hydration at the same time. All you need to do is add a single line of code that hydrates the data into the client. You’ll understand this better once we dig into the setup of the library.
React Frontload uses a reactFrontloadServer
call to render client-side code from the React source, which internally looks for the useFrontload
Hooks. Whenever such a Hook is encountered, its promise is collected. Once all promises are collected, they are awaited and their data is injected into the corresponding components.
Once done, the reactFrontLoadServer
call runs through the source again looking for any new useFrontload
calls that might have originated from the previous round of promise collections. If any new calls are found, the process is repeated with them. If no new calls are encountered, the render is marked as final and sent to the client.
Before you can use React Frontload, there a few configurational changes that you’ll need to make.
Here are the things you need to do:
Wrap your app with the React Frontload provider
import { FrontloadProvider } from 'react-frontload' const App = ({ frontloadState }) => ( <FrontloadProvider initialState={frontloadState}> <Content>...</Content> </FrontloadProvider> )
If you have an SSR app, wrap your existing synchronous server render code with reactFrontloadServerRender
reactFrontloadServerRender
works similarly to how React server rendering works. All it adds is the asynchronous loading part as explained above.
import { renderToString } from 'react-dom/server' import { createFrontloadState, frontloadServerRender } from 'react-frontload' import apiClient from './api_client' app.get('*', async (req, res) => { ... // create a new state object for each render // this will eventually be passed to the FrontloadProvider in <App> const frontloadState = createFrontloadState.server({ // apiClient is a custom implementation of an API handler, that can make HTTP calls, DB operations etc context: { api: apiClient} }) try { // frontloadServerRender is first used to fetch data instead of the generic, straightforward renderToString const { rendered, data } = await frontloadServerRender({ frontloadState, render: () => renderToString(<App frontloadState={frontloadState} />) }) res.send(` <html> ... <!-- server rendered markup --> ${rendered} <!-- loaded data hydration on client --> <script>window._frontloadData=${toSanitizedJSON(data)}</script> ... </html> `) } catch (err) { ... } })
As is evident, frontloadServerRender
returns data as well as the rendered markup. The markup is then sent to the client as-is, while the data is marked to be hydrated.
This is done to ensure that the data associated with the initial view of the component is not reloaded upon client-side rendering, and the prefetched data is injected into the components via initialState
.
Handle the hydration operation on the client-side
The final step in the process for SSR-ready apps is to handle the initial data hydration on the client end.
This can be done with the following code:
import apiClient from './api_client' const frontloadState = createFrontloadState.client({ // use client's implementation of api to load data. context: { api: apiClient }, // hydrate state from SSR serverRenderedData: window._frontloadData }) ... ReactDOM.hydrate(<App frontloadState={frontloadState} />, ...)
That’s it for the initial setup. Now, let’s take a look at how to use this when fetching data inside components.
Here’s a sample component that uses React Frontload to load data from the API asynchronously and handles all possible results of the HTTP request as well:
const Component = () => { const { data, frontloadMeta } = useFrontload('some-component', async ({ api }) => ({ something: await api.fetchSomething() })) if (frontloadMeta.pending) return <div>loading</div> if (frontloadMeta.error) return <div>error</div> return <div>{data.something}</div> }
In the above example, the useFrontload
Hook has been used to get the data as well as a meta object. The data object contains the response from the API call and is defined as an object containing a property, something
, which is fetched by an API client.
Note that this API client (api
) is the same client you would pass in step 3 of the initial setup. A typical API client would expose methods that can be used to perform remote server-based operations and return results and/or data.
While the API client is busy loading data, the meta object has the pending property set to true
, which in the above case is used to show a loading UI to the user. Also, errors can be handled easily by checking for the meta.error
property.
useFrontload
also exposes some other properties, which can come in handy in various situations. For instance, a setData
method is exposed along with the data variable that allows you to update the data object later if you’re planning to fetch more data based on user activity.
This is how it can be implemented:
const Component = () => { // setData is a tiny reducer that can update the data object on the fly. const { data, setData, frontloadMeta } = useFrontload('some-component', async ({ api }) => ({ something: await api.fetchSomething() })) if (frontloadMeta.pending) return <div>loading</div> if (frontloadMeta.error) return <div>error</div> const updateSomething = async () => { try { const updatedSomething = await updateSomething('new value') // API call to update the data setData(data => ({ ...data, something: updatedSomething })) // update data in state } catch { ... } } return ( <> <div>{data.something}</div> <button onClick={updateSomething}>Update!</button> <> )
Seems simple, doesn’t it? Let’s kick things up a notch.
Here are two API calls in one component using React Frontload:
const Component = () => { const { data, frontloadMeta } = useFrontload('some-component', async ({ api }) => ({ something: await api.fetchSomething(), someMoreThings: await api.fetchSomeMoreThings() // just added this as an additional API call })) if (frontloadMeta.pending) return <div>Loading!</div> if (frontloadMeta.error) return <div>Something went wrong</div> return <div>{data.something} and {data.someMoreThings}</div> }
There’s one little thing that we’ve missed out on. If you take a look at the performance of the useFrontload
call as we’re using it in the above example, you’ll notice it is fairly slow.
Apparently, this is because it is awaiting the promises one by one. This can be improved by loading all of them in parallel.
Here’s the fix for that in the same snippet:
const Component = () => { const { data, frontloadMeta } = useFrontload('some-component', async ({ api }) => { const [something, someMoreThings] = await Promise.all([ api.fetchSomething(), api.fetchSomeMoreThings() ]) return { something, someMoreThings } }) if (frontloadMeta.pending) return <div>Loading!</div> if (frontloadMeta.error) return <div>Something went wrong</div> return <div>{data.something} and {data.someMoreThings}</div> }
That’s it. You have blazing-fast data fetching bundled with a state container with its own little reducer to fetch, render, and update data as swiftly as possible.
React Frontload is a handy tool when it comes to handling remote data efficiently, and it only gets better when you consider all of its features. As a React developer, if you’re aiming to scale reliable apps quickly, you’ve got to check out the library at least once. I’m pretty sure it will suit your needs and make your development process a lot faster.
This project is under active development, and a lot of the hottest features have been introduced in the latest versions, so it’s safe to say that it will only get better with time.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowLearn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.
Handle frontend data discrepancies with eventual consistency using WebSockets, Docker Compose, and practical code examples.
Efficient initializing is crucial to smooth-running websites. One way to optimize that process is through lazy initialization in Rust 1.80.