Current section
Files
Jump to
Current section
Files
src/docs@components@pages@state_management.erl
-module(docs@components@pages@state_management).
-compile([no_auto_import, nowarn_unused_vars]).
-export([state_management_page/2]).
-export_type([state_management_page_props/0]).
-type state_management_page_props() :: state_management_page_props.
-spec state_management_page(
sprocket@context:context(),
state_management_page_props()
) -> {sprocket@context:context(), list(sprocket@element:element())}.
state_management_page(Ctx, _) ->
sprocket@component:render(
Ctx,
[sprocket@html:article(
[],
[sprocket@html:h1(
[],
[sprocket@html:text(<<"State Management"/utf8>>)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"State is managed using reducer fuctions and the "/utf8>>
),
sprocket@html:code_text([], <<"reducer"/utf8>>),
sprocket@html:text(
<<" hook. We'll also utilize the "/utf8>>
),
sprocket@html:code_text([], <<"callback"/utf8>>),
sprocket@html:text(
<<" hook to help us dispatch events to the reducer."/utf8>>
)]
),
sprocket@html:h2(
[],
[sprocket@html:text(<<"Reducer Functions"/utf8>>)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"Reducer functions are functions that take a state and message and return a new state. They are used to update state in response to events. Using our "/utf8>>
),
sprocket@html:code_text([], <<"hello_button"/utf8>>),
sprocket@html:text(
<<" component as an example, we can define a function that updates the state when the button is clicked."/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"First we define our state struct and message types:"/utf8>>
)]
),
docs@utils@codeblock:codeblock(
<<"gleam"/utf8>>,
<<"
type Model {
Model(selection: Option(Int), options: List(HelloOption))
}
type Msg {
NoOp
SayHello
}
"/utf8>>
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"Here we're storing a list of options and the index of the selected option in the state."/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"Next we define our update function:"/utf8>>
)]
),
docs@utils@codeblock:codeblock(
<<"gleam"/utf8>>,
<<"
fn update(model: Model, msg: Msg) -> Model {
case msg {
NoOp -> model
SayHello ->
Model(..model, selection: Some(int.random(0, list.length(model.options))))
}
}
"/utf8>>
),
sprocket@html:h2(
[],
[sprocket@html:text(
<<"Introducing the Reducer Hook"/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(<<"Let's declare a "/utf8>>),
sprocket@html:code_text([], <<"reducer"/utf8>>),
sprocket@html:text(
<<" hook in our component that uses our update function:"/utf8>>
)]
),
docs@utils@codeblock:codeblock(
<<"gleam"/utf8>>,
<<"
use ctx, State(Model(selection: selection, options: options), dispatch) <- reducer(
ctx,
initial(hello_options()),
update,
)
"/utf8>>
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"You can see here we got back the current state of the reducer, which we can use in our component. "/utf8>>
),
sprocket@html:text(
<<"Notice, we also got back a "/utf8>>
),
sprocket@html:code_text([], <<"dispatch"/utf8>>),
sprocket@html:text(
<<" function from the reducer. The dispatch function is used to send messages to the reducer which will update the state and trigger a re-render."/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(<<"The use of the "/utf8>>),
sprocket@html:code_text([], <<"initial"/utf8>>),
sprocket@html:text(
<<" function is used to intiialize the state of the reducer. This will only be applied when the component is first rendered."/utf8>>
)]
),
sprocket@html:h2(
[],
[sprocket@html:text(
<<"Introducing the Callback Hook"/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"We need one more thing to complete our component. We need to define a function that will be called when the button is clicked. It's important that we define this as a callback function using the "/utf8>>
),
sprocket@html:code_text([], <<"callback"/utf8>>),
sprocket@html:text(
<<" hook so that we can ensure the id of the callback function is maintained between renders, preventing a new id being created and sent to the client on every update."/utf8>>
)]
),
docs@utils@codeblock:codeblock(
<<"gleam"/utf8>>,
<<"
use ctx, on_say_hello <- callback(
ctx,
CallbackFn(fn() { dispatch(SayHello) }),
WithDeps([]),
)
"/utf8>>
),
sprocket@html:h2(
[],
[sprocket@html:text(<<"Putting it all together"/utf8>>)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"We now have all the pieces we need to create a more interesting button that updates whenever it is clicked. Again, we are using Tailwind CSS to style our button but you can use whichever style framework you prefer."/utf8>>
)]
),
docs@utils@codeblock:codeblock(
<<"gleam"/utf8>>,
<<"
import gleam/int
import gleam/list
import gleam/pair
import gleam/option.{None, Option, Some}
import sprocket/context.{Context}
import sprocket/component.{render}
import sprocket/hooks.{WithDeps}
import sprocket/hooks/reducer.{State, reducer}
import sprocket/hooks/callback.{callback}
import sprocket/internal/identifiable_callback.{CallbackFn}
import sprocket/html.{button, div, span, text}
import sprocket/html/attributes.{class, on_click}
type Model {
Model(selection: Option(Int), options: List(HelloOption))
}
type Msg {
NoOp
SayHello
}
fn update(model: Model, msg: Msg) -> Model {
case msg {
NoOp -> model
SayHello ->
Model(..model, selection: Some(int.random(0, list.length(model.options))))
}
}
fn initial(options: List(HelloOption)) -> Model {
Model(selection: None, options: options)
}
pub type HelloButtonProps {
HelloButtonProps
}
pub fn hello_button(ctx: Context, _props: HelloButtonProps) {
use ctx, State(Model(selection: selection, options: options), dispatch) <- reducer(
ctx,
initial(hello_options()),
update,
)
use ctx, on_say_hello <- callback(
ctx,
CallbackFn(fn() { dispatch(SayHello) }),
WithDeps([]),
)
// find the selected option using the selection index and list of options
let hello =
selection
|> option.map(fn(i) {
list.at(options, i)
|> option.from_result()
})
|> option.flatten()
render(
ctx,
[
div(
[],
[
button(
[
class(\"p-2 bg-blue-500 hover:bg-blue-600 active:bg-blue-700 text-white rounded\"),
on_click(on_say_hello),
],
[text(\"Say Hello!\")],
),
..case hello {
None -> []
Some(hello) -> [
span([class(\"ml-2\")], [text(pair.second(hello))]),
span(
[class(\"ml-2 text-gray-400 bold\")],
[text(pair.first(hello))],
),
]
}
],
),
],
)
}
type HelloOption =
#(String, String)
fn hello_options() -> List(HelloOption) {
[
#(\"English\", \"Hello\"),
#(\"Spanish\", \"Hola\"),
#(\"French\", \"Bonjour\"),
#(\"German\", \"Hallo\"),
#(\"Italian\", \"Ciao\"),
#(\"Portuguese\", \"Olá\"),
#(\"Hawaiian\", \"Aloha\"),
#(\"Chinese (Mandarin)\", \"你好,(Nǐ hǎo)\"),
#(\"Japanese\", \"こんにち, (Konnichiwa)\"),
#(\"Korean\", \"안녕하세, (Annyeonghaseyo)\"),
#(\"Arabic\", \"مرحب, (Marhaba)\"),
#(\"Hindi\", \"नमस्त, (Namaste)\"),
#(\"Turkish\", \"Merhaba\"),
#(\"Dutch\", \"Hallo\"),
#(\"Swedish\", \"Hej\"),
#(\"Norwegian\", \"Hei\"),
#(\"Danish\", \"Hej\"),
#(\"Greek\", \"Γεια σας,(Yia sas)\"),
#(\"Polish\", \"Cześć\"),
#(\"Swahili\", \"Hujambo\"),
]
}
"/utf8>>
),
docs@utils@common:example(
[sprocket@component:component(
fun docs@components@say_hello:say_hello/2,
say_hello_props
)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"We now have a functional button that says hello in a different language when it's clicked."/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"Remember, all of these state changes are happening on the server. Events are being passed from the client to the server, the latest view is rendered and a minimal diff update is sent back to the client a which is then patched into the DOM. Very cool!"/utf8>>
)]
),
sprocket@html:p(
[],
[sprocket@html:text(
<<"These are just two of the hooks that are available in Sprocket. There are many more to explore! We'll cover hooks more in-depth in the next section."/utf8>>
)]
)]
)]
).