aboutsummaryrefslogtreecommitdiff
path: root/pages/guide
diff options
context:
space:
mode:
Diffstat (limited to 'pages/guide')
-rw-r--r--pages/guide/02-state-management.md84
1 files changed, 82 insertions, 2 deletions
diff --git a/pages/guide/02-state-management.md b/pages/guide/02-state-management.md
index a9b52c5..b7aa31a 100644
--- a/pages/guide/02-state-management.md
+++ b/pages/guide/02-state-management.md
@@ -1,10 +1,71 @@
# 02 State management
We saw in the qucikstart guide that all Lustre applications are built around the
-Model View Update (MVU) architecture. This means that the state of the application
+Model-View-Update (MVU) architecture. This means that the state of the application
is stored in a single, immutable data structure called the model, and updated as
messages are dispatched to the runtime.
+The MVU architecture is an example of _unidirectional data flow_:
+
+- Your model describes the entire state of your application at a given point in
+ time.
+
+- The UI is a pure function of that model: if the model doesn't change, the UI
+ doesn't change.
+
+- Events from the outside world – user interaction, HTTP responses, ... – send
+ messages to an update function that constructs a new model.
+
+- The UI re-renders based on the new state.
+
+This is in contrast to _bidirectional_ approaches to state management, where the
+UI can modify state directly. For some developers this can be a difficult idea
+to get used to, but it brings a number of benefits:
+
+- A **single source of truth** makes it easier to reason about the state of your
+ application. State management is lifted _out_ of UI code, letting it focus just
+ on presentation and making it easier to test and refactor.
+
+- Message-driven **declarative state updates** give you a hollistic view of how
+ your application can change over time. Tracking incoming messages gives you a
+ history of state updates and can be serialised and logged for debugging or
+ testing purposes.
+
+- State updates are **pure**. We will learn more about this in the [next guide](./03-side-effects),
+ but for now it is enough to know that this means testing your state changes is
+ much easier because mocking messages is simpler tham mocking side effects!
+
+The rest of this guide contains some learned-wisdom and best practices for managing
+state in Lustre applications.
+
+## The best model is not always a record
+
+It is overwhelmingly common to see the model of a Lustre application as a single
+record. This is a sensible place to start, but there are other options! Gleam's
+custom types allow us to model our data as disjoint variants. Using these as your
+application's model can be particularly useful when you have different states that
+do not need to persist across navigations:
+
+```gleam
+type Model {
+ LoggedIn(LoggedInModel)
+ Public(PublicModel)
+}
+
+type LoggedInModel {
+ ...
+}
+
+type PublicModel {
+ ...
+}
+```
+
+Here, we a model that represents our application as either having a logged in user
+or just one of the public routes. This pushes us towards the great practice of
+[making impossible states impossible](https://github.com/stereobooster/pragmatic-types/blob/master/posts/making-impossible-states-impossible.md).
+Now, we can write separate
+
## Messages not actions
Lustre is not the first frontend framework to use the MVU architecture or to
@@ -14,7 +75,26 @@ though, and these libraries often talk in terms of _actions_ but you'll see
Elm and Lustre prefer the term _message_.
Actions frame incoming events as _things to do_: "add a new todo", "make an HTTP
-request", etc.
+request", etc. This can work well in the beginning, but as your application grows
+and the number of things you can do grows, naming messages as actions can become
+problematic.
+
+In particular, it encourages you to recursively call your `update` function with
+different messages when you want to compose behaviour. Gleam is a functional
+programming language: we should use functions to update our state, not message
+dispatching! Communicating through messages is a way for the _outside world_ to
+talk to our application, not for our applications to talk to themselves.
+
+A recursive update function makes it difficult to see the consequences of any one
+message as you need to trace through the recursive calls in your head to understand
+which messages are being dispatched and in what order.
+
+It is important to note that on occasion, framing messages as actions _does_ make
+more sense. In the [interactivity example](https://github.com/lustre-labs/lustre/tree/main/examples/02-interactivity)
+we have messages like `Incr` and `Decr`. These messages don't get any clearer if
+we opt for a name like `IncrButtonClicked` but as the examples grow in complexity,
+such as the [HTTP example](https://github.com/lustre-labs/lustre/tree/main/examples/05-http-requests),
+message names tend to be more descriptive – `GotMessage` – and less action-oriented.
## View functions not components