@sync
Easy data synchronization with support for custom data types.
The @sync
annotation gives you an easy way to sync state of a StatefulComponent
from the server to the client.
For a general guide about data-fetching on the server see Data Fetching.
Usage
Start by annotating a field of your StatefulComponent
with @sync
:
class MyComponent extends StatefulComponent { /* ... */ }
class MyComponentState extends State<MyComponent> {
// Use @sync on any field to automatically sync its value from the server to the client.
@sync
int myValue = 0;
}
Then when running jaspr serve
or jaspr build
this will generate a .sync.dart
alongside your component including
a <ComponentStateName>SyncMixin
that you have to apply to your state class:
import 'my_component.sync.dart';
class MyComponent extends StatefulComponent { /* ... */ }
class MyComponentState extends State<MyComponent> with MyComponentStateSyncMixin {
@sync
int myValue = 0;
}
You can also use the @sync
annotation on multiple fields of the State
class.
Custom Data Types
When using @sync
on a field, that field is
- serialized on the server during pre-rendering as part of the html output
- deserialized on the client during
initState()
when this component is first mounted
Therefore, any annotated field must either have a primitive serializable type: bool
, int
, double
, String
or List
/ Map
s of these.
Or you can use custom data types by using the @encoder
and @decoder
annotations with the class:
class Model {
@decoder
static Model fromJson(Map<String, dynamic> json) => /* ... */;
@encoder
Map<String, dynamic> toJson() => /* ... */;
}
Read the docs on the @encoder / @decoder annotations to learn more about how to set up serialization for custom data types.
With this setup you can use fields of any type with the @sync
annotation.
Preloading Data
When using the @sync
annotation, it is common that you want to preload the data that you then want to sync to the client.
When the data is synchronous you can simply assign the value of a @sync
field in the initState()
method:
import 'my_component.sync.dart';
class MyComponent extends StatefulComponent { /* ... */ }
class MyComponentState extends State<MyComponent> with MyComponentStateSyncMixin {
@sync
int myValue = 0;
@override
void initState() {
// On the client, [myValue] and any other synced field will be assigned
// the synced value during the [super.initState()] call.
super.initState();
// On the server, we can assign the field during pre-rendering.
if (!kIsWeb) {
myValue = getValueOnServer();
}
}
// You can modify any synced value on the client as usual.
// @sync is only for setting the initial value of a field on the client to a value that has
// been set during the pre-rendering on the server.
void increment() {
setState(() {
myValue++;
});
}
}
When the data you want to load is asynchronous, you can additionally use the PreloadStateMixin
to perform any asynchronous work before initState()
is called on the server:
import 'my_component.sync.dart';
class MyComponent extends StatefulComponent { /* ... */ }
class MyComponentState extends State<MyComponent> with MyComponentStateSyncMixin, PreloadStateMixin {
@sync
int myValue = 0;
// Perform any async work on the server.
// This will delay `initState()` and the building of this component until the future is completed.
Future<void> preloadState() async {
// Load some async data, e.g. from a database.
myValue = await getValueFromDatabase();
}
}
Read the docs on PreloadStateMixin for more details.
Alternatives
-
Check out the SyncStateMixin for a more explicit way to sync state of a
StatefulComponent
between server and client (this is used by@sync
under the hood). -
Check out @client components, that support passing data from the server to the client as part of the component parameters, including support for custom data types.