Data Fetching
Fetch data on the server during pre-rendering and sync data to the client.
This page is only relevant when using static or server mode.
When pre-rendering your website on the server using either static or server mode, it is common to load some data for
example from a database or external api in oder to render out the content of your site. Loading data like this is usually
asynchronously and using server-side only apis like dart:io
. Therefore:
- You need to delay the pre-rendering of your site until all needed data is loaded.
- Correctly handle server-side logic without breaking client-side compilation.
- Make sure the loaded data is also available on the client for further client-side rendering.
Say you are developing a blog and want to show a list of articles on the website. Your articles are stored in some database or CMS, and you also want the user to be able to filter the list on the client. This requires the following data-loading strategy:
- Asynchronously load the list of articles on the server during pre-rendering.
- Pre-render the list of articles to the initial html of the website.
- Additionally send the full list of articles as data to the client.
- Hydrate the website on the client to enable interactivity.
- When the user wants to filter the articles use the received article data to update the website content on the client.
With Jaspr loading data like this on the server and also syncing it with the client is build into the core framework, and you have a couple of options to choose from.
Loading data on the server
The main challenge when loading data on the server is to delay the pre-rendering until all data is loaded. Otherwise, it wouldn't be part of the initial html of the website.
Jaspr has two main concepts that solve this:
Asynchronous Components
Jaspr provides two special components that effectively have an asynchronous build method. This allows you to do any asynchronous work during build, and also delay the build until the work is completed.
These components can only be used on the server and are only available using the
package:jaspr/server.dart
import.
One of these, the AsyncBuilder
component, can be used like this:
yield AsyncBuilder(builder: (context) async* {
// Simply use "await" inside the build method.
var data = await loadDataFromDatabase();
// Renders the component after the data has loaded.
yield MyOtherComponent(data: data);
});
Here you directly use await
inside the build method. This may feel very strange or dangerous as a Flutter developer.
Be assured that this is perfectly safe, mainly for the reason that this component is only executed on the server and
only built once during pre-rendering. Therefore, you don't need to worry about potentially expensive rebuilds or floating async
operations with these components.
The other async component is the AsyncStatelessComponent
, a variant of the StatelessComponent
but with an async build method:
class MyAsyncComponent extends AsyncStatelessComponent {
@override
Stream<Component> build(BuildContext context) async* {
// Simply use "await" inside the build method.
var data = await loadDataFromDatabase();
// Renders the component after the data has loaded.
yield MyOtherComponent(data: data);
}
}
Learn more about how to use the AsyncBuilder and AsyncStatelessComponent.
Preloading state of a StatefulComponent
If you have a StatefulComponent
you can also load asynchronous data using the PreloadStateMixin
:
class MyStatefulComponent extends StatefulComponent { /* ... */ }
class MyState extends State<MyStatefulComponent> with PreloadStateMixin {
@override
Future<void> preloadState() async {
/* ... */
}
}
The preloadState()
method will only be executed on the server and will be called before initState()
. It will defer
initState()
as well as building the component until the future returned by the method is completed.
Learn more about how to use the PreloadStateMixin.
Syncing data to the client
If you want to have an interactive website it's often not enough to only pre-render the website with the loaded data. The used data also needs to be available on the client in order to update the data and re-render the website on user interactions.
Therefore, it's often needed to also synchronize the data that's loaded on the server with the client. Jaspr has this built into the framework in a couple of ways.
The synchronization of data in Jaspr is only one-directional (from the server to the client) and is only performed once for the initial rendering of the website. If you need to send data from the client to the server, or need a more continuous loading of data, you should use a normal server-side api or realtime communication system like websockets.
Passing data through @client components
@client
components are special annotated components that are automatically hydrated on the client to make your website
interactive. In addition to being a way to hydrate you website, they can also be used to pass data from the server to
the client.
When using such a component, all parameters of the component are serialized and sent to the client. When the component is first built during hydration, it receives all the same parameters on the client that have been previously used on the server during pre-rendering.
@client
class App extends StatelessComponent {
// When this component is hydrated on the client, all parameters are passed
// the same values as have been used during pre-rendering on the server.
const App({required this.title, super.key});
final String title;
/* ... */
}
Read more about @client components and how they can be used to sync data from the server to the client.
Using @sync on fields on a StatefulComponents
Alternatively, you can use the @sync
annotation on any field of a StatefulComponent
s State
class which will
cause that field to be synced to the client and have the same value on the client as during pre-rendering on the server.
class MyComponent extends StatefulComponent { /* ... */ }
class MyComponentState extends State<MyComponent> {
// This field will automatically sync its value from the server to the client.
@sync
int myValue = 0;
}
Read more about the @sync annotation and how it syncs any field from the server to the client.
Additionally check out the SyncStateMixin which is used internally by the @sync
annotation
and can also be used directly to sync state of a StatefulComponent
in a more flexible and explicit way.