From cc9b7df2347916082d6ad4d36530891a4ca4c849 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Mon, 12 Sep 2022 10:21:02 -0400 Subject: [PATCH 01/32] Initial commit of blog post --- ...ssing-callback-params-to-machine-hooks.mdx | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx new file mode 100644 index 00000000..66a5b068 --- /dev/null +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -0,0 +1,20 @@ +--- +title: Passing callback params to machine hooks +description: >- + We'll explore how and why to pass callback functions as params to your custom + machine hooks. +tags: + - callbacks + - async + - debugging + - actions + - stately + - xstate + - react +author: + - - Kevin Maes +originalURL: '' +excerpt: '' +publishedAt: 2022-9-12 +--- +Here goes the content of your post From 477a33a1093b2a66b2f143ecbb3389f1c16d7175 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Mon, 12 Sep 2022 17:24:50 -0400 Subject: [PATCH 02/32] Initial draft of the post and its examples --- ...ssing-callback-params-to-machine-hooks.mdx | 300 +++++++++++++++++- 1 file changed, 297 insertions(+), 3 deletions(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 66a5b068..c3dfe46f 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -13,8 +13,302 @@ tags: - react author: - - Kevin Maes -originalURL: '' -excerpt: '' +originalURL: "" +excerpt: "" publishedAt: 2022-9-12 --- -Here goes the content of your post + +### Introduction + +Are you a React developer using [XState](https://xstate.js.org/) to model your application logic? In a previous post I wrote about how to [create custom XState machine hooks for use in a React application](https://stately.ai/blog/just-use-hooks-xstate-in-react-components) and, as an example, I referenced a basic implementation of [a `useToggleMachine` hook](https://codesandbox.io/s/usetogglemachine-example-1-lazy-machine-8zcbvs?file=/src/Toggler.tsx). In this post we'll take the next step and explore the idea of **passing callback functions as params** to our custom machine hooks. + +### What does passing callback params look like? + +In our previous example we passed in a `initialActive` boolean parameter to the `useToggleMachine` hook. We can use the same pattern to pass in a callback function. Let's add a `onToggle` callback to the hook's params: + +```ts +export const useToggleMachine = ( + initialActive: boolean, + onToggle: (isActive: boolean) => void +) => { + // ... +}; +``` + +We should decide if that callback will be required or optional which will likely depend on whether the component is solely responsible for implementing the callback's functionality or if the callback is meant to augment the implementation of the hook. In our example, since we're really just notifying the component of a state change that's definitely maintained by the state machine hook, we'll make the callback optional. + +```ts +export const useToggleMachine = ( + initialActive: boolean, + onToggle?: (isActive: boolean) => void +) => { + // ... +}; +``` + +Notice that we've also typed the function signature with TypeScript, indicating that the callback will be invoked with a single `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we're interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine's current state. + +### Why pass callbacks? + +Part of a hook's purpose is encapsulation, and it's quite possible that your state machine and its surrounding hook do a fine job of handling everything without the need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let's looks at a few of these. + +(handle effects, UI updates, or functionality _outside_ of your machine hook.) + +#### Responding to a machine's state changes + +In the previous post I showed how you can return an `isActive` boolean flag from the `useToggleMachine` hook so that the parent component can use it to conditionally render a representation of the toggle state. + +```tsx +const [isActive, toggle] = useToggleMachine(); +``` + +However, using a callback allows the hook to notify us of a state change the moment it happens or as soon as a state transition completes. This saves the component from needing to compare the current value of `isActive` to the previous value in order to react to a change. + +#### Handling async server calls or external API calls + +It is also possible that you might choose to handle async calls to a server outside of the machine hook. There could be another non-machine hook your component uses to actually make the call, some flavor of `useQuery` or `useMutation` from libraries like [SWR](https://swr.vercel.app/), [React Query](https://react-query.tanstack.com/), or [Apollo Client](https://www.apollographql.com/docs/react/). In this case, you might want to pass a callback to the machine hook that should be called when the machine enters a particular state. + +#### Error handling, logging, and debugging + +Receiving notification when your callback is invoked lets you handle errors and other events. This might mean showing a toast, logging to an external service, or it may allow you to temporarily debug your machine without digging into the code of the hook or the machine itself. + +#### Resusability + +Additionally, by abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You'll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. + +### Adding actions to the machine + +In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, regardless of in which state it happens, we could register an `onToggle` action. + +```ts +export const toggleMachine = { + id: "toggle", + initial: "inactive", + states: { + inactive: { + on: { + TOGGLE: { + // Define an array of actions to be performed when the TOGGLE event is received + actions: ["notifyOnToggle"], + target: "active", + }, + }, + }, + active: { + on: { + TOGGLE: { + // Same for this state. + actions: ["notifyOnToggle"], + target: "inactive", + }, + }, + }, + }, +}; +``` + +There are a few things about actions that are worth noting in regards to how I'll be using them here. + +First, it's good to adhere to XState best practices by referencing the action name as a string value instead of inlining functions. This allows us to define the actions in a separate object which we can [pass into `useMachine` as the second argument](https://xstate.js.org/docs/packages/xstate-react/#usemachine-machine-options). + +The [documentation about actions](https://xstate.js.org/docs/guides/actions.html#api) also mentions the following (I'll paraphrase): + +1. Actions are usually synchronous or at least they should be treated as such as we should not `await` their completion. +2. Actions are treated as fire and forget. +3. Actions should not directly impact the system should they fail. + +### Invoking the callback param from actions + +In our options object, the second argument to `useMachine`, we can then define the actual implementation of our `notifyOnToggle` action which is really to just call our `onToggle` callback with a boolean indicating our `isActive` status. + +```ts +const [state, send] = useMachine( + () => + createMachine({ + ...toggleMachine, + initial: "inactive", + }), + { + actions: { + notifyOnToggle: (context, event, meta) => { + onToggle?.(meta.state.matches("active")); + }, + }, + } +); +``` + +Things could get even more interesting if we were to modify the signature of `onToggle` and pass it other values from `context` or from `event`. + +```ts +// Example +onToggle({ + isActive: meta.state.matches("active"), + numOfToggles: context.numChange, +}); +``` + +If the signature of `onToggle` matches that of an XState action, you could even pass the entire action implementation in as the callback itself. + +```ts +{ + actions: { + notifyOnToggle: onToggle, + }, +} +``` + +Another more explicit route could be to pass in multiple callback params and map them to discrete actions. + +When responding to the TOGGLE event in our `toggleMachine` example, we could add an `activate` action to the `inactive` state and also a `deactivate` action to the `active` state. + +```ts +export const toggleMachine = { + id: "toggle", + initial: "inactive", + states: { + inactive: { + on: { + TOGGLE: { + // Define an array of actions to be performed when the TOGGLE event is received + actions: ["activate"], + target: "active", + }, + }, + }, + active: { + on: { + TOGGLE: { + // Same for this state. + actions: ["deactivate"], + target: "inactive", + }, + }, + }, + }, +}; +``` + +We could then pass in two callbacks to the hook. + +```ts +export const useToggleMachine = ( + initialActive: boolean, + onActivate: () => void + onDeactivate: () => void +) => { + // ... +}; +``` + +And call them from their respective actions without having to pass in any additional arguments indicating the current state since it's understood from the separation of the two callbacks. + +```ts +const [state, send] = useMachine( + () => + createMachine({ + ...toggleMachine, + initial: "inactive", + }), + { + actions: { + activate: (context, event) => { + onActivation?.(); + }, + deactivate: (context, event) => { + onDeactivation?.(); + }, + }, + } +); +``` + +One more thing to notice is that none of the examples above have us waiting for our callback to complete. In fact, our callback consistently returns `void` according to its signature - returning neither a value nor a promise-wrapped value. In that way we've implemented our action as a fire-and-forget operation. + +### Using `invoke` to handle async actions + +But what if we really do care about the result of our callback? We said we might use a callback to make an async call to a server and in that case we would be interested in the result of that call. In that case, we could use a machine's `invoke` feature to handle the async call properly. + +```ts +export const toggleMachine = { + id: "toggle", + initial: "inactive", + states: { + inactive: { + on: { + TOGGLE: { + target: "togglingActive", + }, + }, + }, + active: { + // ... + }, + togglingActive: { + invoke: { + id: "togglingActive", + src: "togglingActive", + }, + onDone: { + target: "active", + }, + onError: { + target: "inactive", + }, + }, + }, +}; +``` + +We can now pass in an async `doToggle` function as our callback param, provided that it returns a promise. + +```ts +export const useToggleMachine = ( + initialActive: boolean, + doToggle: async () => Promise +) => { + // ... +}; +``` + +Here is our `togglingActive` definition under `services` in our options object, passed as the second argument to `useMachine`. The source of the `toggleActive` invoke is our async callback function. + +```ts +const [state, send] = useMachine( + () => + createMachine({ + ...toggleMachine, + initial: "inactive", + }), + { + invoke: { + togglingActive: doToggle, + }, + } +); +``` + +Regardless of whether `doToggle` succeeds or fails, we will transition the machine to the correct state as defined in the machine's config. + +### The good and the bad + +#### Benefits + +We now have a way to tap into our hook either to be notified of internal changes as they occur or to be able to effect change or delegate that responsibility elsewhere. In most of the examples above, our component (and its authors) still don't need to know much about the workings of XState if using an existing machine hook that already connects the callback param(s) to the machine's actions or invoke. They pass in a function and the function will get called at the appropriate time. + +#### Drawbacks + +Adding callback params to your custom machine hook does increase the surface area of its API and there are are tradeoffs in complexity and duties. + +Your component is no longer just a dumb component that renders based on the state of the machine. It is now a bit more involved in handling or delegating effect management. + +However, we can remind ourselves as to what the machine and our hook still do for us: + +- Establishes the possible states and defines transitions between states +- Manages how and when those state transitions happen +- Handles the change of our context values +- Allows us to map our callbacks and outward facing effects to everything that's predictable in the machine. + +### Conclusion + +Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this pattern or other similar patterns then I'd love to hear about your experiences! From 4de52fd7cd0abffa357a8b4781f544cada3fc9d2 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Mon, 12 Sep 2022 18:24:47 -0400 Subject: [PATCH 03/32] Revise and edit the copy --- ...ssing-callback-params-to-machine-hooks.mdx | 79 +++++++++++-------- 1 file changed, 47 insertions(+), 32 deletions(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index c3dfe46f..a02ad28f 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -24,9 +24,13 @@ Are you a React developer using [XState](https://xstate.js.org/) to model your a ### What does passing callback params look like? -In our previous example we passed in a `initialActive` boolean parameter to the `useToggleMachine` hook. We can use the same pattern to pass in a callback function. Let's add a `onToggle` callback to the hook's params: +In our previous example we passed in a `initialActive` boolean parameter to the `useToggleMachine` hook. ```ts +// Component +const [isActive, toggle] = useToggleMachine(); + +// Custom machine hook export const useToggleMachine = ( initialActive: boolean, onToggle: (isActive: boolean) => void @@ -35,50 +39,60 @@ export const useToggleMachine = ( }; ``` -We should decide if that callback will be required or optional which will likely depend on whether the component is solely responsible for implementing the callback's functionality or if the callback is meant to augment the implementation of the hook. In our example, since we're really just notifying the component of a state change that's definitely maintained by the state machine hook, we'll make the callback optional. +We can use the same pattern to pass in a callback function. Let's add an `onToggle` callback to the hook's params: ```ts export const useToggleMachine = ( initialActive: boolean, + onToggle: (isActive: boolean) => void +) => { + // ... +}; +``` + +Notice that we've also typed the function signature with TypeScript. Since we'll use it to notify our component of a state change that's inherently maintained by the machine hook, we'll consider it optional. + +```ts +export const useToggleMachine = ( + initialActive: boolean, + // TypeScript will infer that this is optional onToggle?: (isActive: boolean) => void ) => { // ... }; ``` -Notice that we've also typed the function signature with TypeScript, indicating that the callback will be invoked with a single `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we're interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine's current state. +The callback's signature also includes an `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we're interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine's current state. ### Why pass callbacks? -Part of a hook's purpose is encapsulation, and it's quite possible that your state machine and its surrounding hook do a fine job of handling everything without the need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let's looks at a few of these. - -(handle effects, UI updates, or functionality _outside_ of your machine hook.) +Part of a hook's purpose is encapsulation. If your state machine and hook handle everything internally then you don't need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let's looks at a few of these. -#### Responding to a machine's state changes +#### Getting notified about state machine changes -In the previous post I showed how you can return an `isActive` boolean flag from the `useToggleMachine` hook so that the parent component can use it to conditionally render a representation of the toggle state. +We already get an `isActive` boolean flag from the `useToggleMachine` hook so the component can conditionally render a representation of the toggle state. ```tsx const [isActive, toggle] = useToggleMachine(); ``` -However, using a callback allows the hook to notify us of a state change the moment it happens or as soon as a state transition completes. This saves the component from needing to compare the current value of `isActive` to the previous value in order to react to a change. +However, using a callback alerts the component of a state changes the moment it happens, as soon as a transition completes. This saves the component from needing to compare the current value of `isActive` to the previous value in order to respond to a change. #### Handling async server calls or external API calls -It is also possible that you might choose to handle async calls to a server outside of the machine hook. There could be another non-machine hook your component uses to actually make the call, some flavor of `useQuery` or `useMutation` from libraries like [SWR](https://swr.vercel.app/), [React Query](https://react-query.tanstack.com/), or [Apollo Client](https://www.apollographql.com/docs/react/). In this case, you might want to pass a callback to the machine hook that should be called when the machine enters a particular state. +We might choose to handle async calls to a server outside of our machine hook, possibly using an async hook like `useQuery` or `useMutation` from libraries like [SWR](https://swr.vercel.app/), [React Query](https://react-query.tanstack.com/), or [Apollo Client](https://www.apollographql.com/docs/react/). In this case, we could pass a callback to the machine hook that should be called when its machine enters a particular state. #### Error handling, logging, and debugging -Receiving notification when your callback is invoked lets you handle errors and other events. This might mean showing a toast, logging to an external service, or it may allow you to temporarily debug your machine without digging into the code of the hook or the machine itself. +Receiving notification when your callback is invoked lets you handle errors and other events. We could show a toast, log to an external service, or temporarily debug our machine without digging into the code of the hook or the machine itself. #### Resusability -Additionally, by abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You'll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. +By abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You'll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. ### Adding actions to the machine -In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, regardless of in which state it happens, we could register an `onToggle` action. +In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, we can register an `onToggle` action. ```ts export const toggleMachine = { @@ -107,19 +121,15 @@ export const toggleMachine = { }; ``` -There are a few things about actions that are worth noting in regards to how I'll be using them here. +According to the [docs](https://xstate.js.org/docs/guides/actions.html#api), actions are (paraphrasing): -First, it's good to adhere to XState best practices by referencing the action name as a string value instead of inlining functions. This allows us to define the actions in a separate object which we can [pass into `useMachine` as the second argument](https://xstate.js.org/docs/packages/xstate-react/#usemachine-machine-options). +1. Usually synchronous (at least, we should not `await` their completion). +2. Treated as fire and forget. +3. Not supposed to directly impact the system, should they fail. -The [documentation about actions](https://xstate.js.org/docs/guides/actions.html#api) also mentions the following (I'll paraphrase): +### Wiring up the callback param from actions -1. Actions are usually synchronous or at least they should be treated as such as we should not `await` their completion. -2. Actions are treated as fire and forget. -3. Actions should not directly impact the system should they fail. - -### Invoking the callback param from actions - -In our options object, the second argument to `useMachine`, we can then define the actual implementation of our `notifyOnToggle` action which is really to just call our `onToggle` callback with a boolean indicating our `isActive` status. +In our [options object](https://xstate.js.org/docs/packages/xstate-react/#usemachine-machine-options), the second argument to `useMachine`, we can define the implementation of our `notifyOnToggle` action. In our case, this is really a call to the `onToggle` callback with a boolean arg indicating our `isActive` status. ```ts const [state, send] = useMachine( @@ -153,14 +163,15 @@ If the signature of `onToggle` matches that of an XState action, you could even ```ts { actions: { + // onToggle will be called with context, event, and meta args. notifyOnToggle: onToggle, }, } ``` -Another more explicit route could be to pass in multiple callback params and map them to discrete actions. +To be even more explicit, we could pass in multiple callback params and map them to discrete actions. -When responding to the TOGGLE event in our `toggleMachine` example, we could add an `activate` action to the `inactive` state and also a `deactivate` action to the `active` state. +When responding to the TOGGLE event in our `toggleMachine` example, we can add an `activate` action to the `inactive` state and also a `deactivate` action to the `active` state. ```ts export const toggleMachine = { @@ -201,7 +212,7 @@ export const useToggleMachine = ( }; ``` -And call them from their respective actions without having to pass in any additional arguments indicating the current state since it's understood from the separation of the two callbacks. +We then call the callbacks from their respective actions without passing arguments indicating current state. ```ts const [state, send] = useMachine( @@ -223,11 +234,11 @@ const [state, send] = useMachine( ); ``` -One more thing to notice is that none of the examples above have us waiting for our callback to complete. In fact, our callback consistently returns `void` according to its signature - returning neither a value nor a promise-wrapped value. In that way we've implemented our action as a fire-and-forget operation. +Again, none of the examples above have us waiting for our callback to complete. In fact, our callback's signature indicates a return value of `void` (neither value nor promise). ### Using `invoke` to handle async actions -But what if we really do care about the result of our callback? We said we might use a callback to make an async call to a server and in that case we would be interested in the result of that call. In that case, we could use a machine's `invoke` feature to handle the async call properly. +But what if we really do care about the result of our callback? If we use a callback to make an async call to a server then we would surely be interested in the result of that call. In that case, we could use a machine's `invoke` feature to handle the async call properly. ```ts export const toggleMachine = { @@ -260,7 +271,7 @@ export const toggleMachine = { }; ``` -We can now pass in an async `doToggle` function as our callback param, provided that it returns a promise. +Our hook now accepts an async `doToggle` function as a callback param, provided that it returns a promise. ```ts export const useToggleMachine = ( @@ -294,20 +305,24 @@ Regardless of whether `doToggle` succeeds or fails, we will transition the machi #### Benefits -We now have a way to tap into our hook either to be notified of internal changes as they occur or to be able to effect change or delegate that responsibility elsewhere. In most of the examples above, our component (and its authors) still don't need to know much about the workings of XState if using an existing machine hook that already connects the callback param(s) to the machine's actions or invoke. They pass in a function and the function will get called at the appropriate time. +We now have a way to tap into our hook either to be notified of state changes or to directly effect change (or delegate responsibility elsewhere). + +In our examples, the component (and its authors) still don't need to know much about the workings of XState which depending on the knowledge and makeup of a team, could be an advantage. + +As long as the hook connects the callback param to the machine's actions or invokes, the component and even its effects should stay in sync with the machine's state. #### Drawbacks Adding callback params to your custom machine hook does increase the surface area of its API and there are are tradeoffs in complexity and duties. -Your component is no longer just a dumb component that renders based on the state of the machine. It is now a bit more involved in handling or delegating effect management. +Your component, no longer only a dumb renderer of machine state, is now a bit more involved in handling or delegating effect management. However, we can remind ourselves as to what the machine and our hook still do for us: - Establishes the possible states and defines transitions between states - Manages how and when those state transitions happen - Handles the change of our context values -- Allows us to map our callbacks and outward facing effects to everything that's predictable in the machine. +- Allows us to map our callbacks and outward facing effects to the predictable mechanisms of the machine. ### Conclusion From 97a7416edc718f814e8abf8ebf7e98faba454e9e Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Mon, 12 Sep 2022 20:29:03 -0400 Subject: [PATCH 04/32] Add backticks for TOGGLE --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index a02ad28f..6b24caba 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -171,7 +171,7 @@ If the signature of `onToggle` matches that of an XState action, you could even To be even more explicit, we could pass in multiple callback params and map them to discrete actions. -When responding to the TOGGLE event in our `toggleMachine` example, we can add an `activate` action to the `inactive` state and also a `deactivate` action to the `active` state. +When responding to the `TOGGLE` event in our `toggleMachine` example, we can add an `activate` action to the `inactive` state and also a `deactivate` action to the `active` state. ```ts export const toggleMachine = { From c677aaea68d84d8fb61288f8b4b898f3049f82ec Mon Sep 17 00:00:00 2001 From: laurakalbag Date: Tue, 13 Sep 2022 09:51:11 +0100 Subject: [PATCH 05/32] Use curly quotes for apostrophes --- ...ssing-callback-params-to-machine-hooks.mdx | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 6b24caba..32e817fe 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -1,7 +1,7 @@ --- title: Passing callback params to machine hooks description: >- - We'll explore how and why to pass callback functions as params to your custom + We’ll explore how and why to pass callback functions as params to your custom machine hooks. tags: - callbacks @@ -20,7 +20,7 @@ publishedAt: 2022-9-12 ### Introduction -Are you a React developer using [XState](https://xstate.js.org/) to model your application logic? In a previous post I wrote about how to [create custom XState machine hooks for use in a React application](https://stately.ai/blog/just-use-hooks-xstate-in-react-components) and, as an example, I referenced a basic implementation of [a `useToggleMachine` hook](https://codesandbox.io/s/usetogglemachine-example-1-lazy-machine-8zcbvs?file=/src/Toggler.tsx). In this post we'll take the next step and explore the idea of **passing callback functions as params** to our custom machine hooks. +Are you a React developer using [XState](https://xstate.js.org/) to model your application logic? In a previous post I wrote about how to [create custom XState machine hooks for use in a React application](https://stately.ai/blog/just-use-hooks-xstate-in-react-components) and, as an example, I referenced a basic implementation of [a `useToggleMachine` hook](https://codesandbox.io/s/usetogglemachine-example-1-lazy-machine-8zcbvs?file=/src/Toggler.tsx). In this post we’ll take the next step and explore the idea of **passing callback functions as params** to our custom machine hooks. ### What does passing callback params look like? @@ -39,7 +39,7 @@ export const useToggleMachine = ( }; ``` -We can use the same pattern to pass in a callback function. Let's add an `onToggle` callback to the hook's params: +We can use the same pattern to pass in a callback function. Let’s add an `onToggle` callback to the hook’s params: ```ts export const useToggleMachine = ( @@ -50,7 +50,7 @@ export const useToggleMachine = ( }; ``` -Notice that we've also typed the function signature with TypeScript. Since we'll use it to notify our component of a state change that's inherently maintained by the machine hook, we'll consider it optional. +Notice that we’ve also typed the function signature with TypeScript. Since we’ll use it to notify our component of a state change that’s inherently maintained by the machine hook, we’ll consider it optional. ```ts export const useToggleMachine = ( @@ -62,11 +62,11 @@ export const useToggleMachine = ( }; ``` -The callback's signature also includes an `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we're interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine's current state. +The callback’s signature also includes an `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we’re interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine’s current state. ### Why pass callbacks? -Part of a hook's purpose is encapsulation. If your state machine and hook handle everything internally then you don't need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let's looks at a few of these. +Part of a hook’s purpose is encapsulation. If your state machine and hook handle everything internally then you don’t need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let’s looks at a few of these. #### Getting notified about state machine changes @@ -88,7 +88,7 @@ Receiving notification when your callback is invoked lets you handle errors and #### Resusability -By abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You'll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. +By abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You’ll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. ### Adding actions to the machine @@ -234,11 +234,11 @@ const [state, send] = useMachine( ); ``` -Again, none of the examples above have us waiting for our callback to complete. In fact, our callback's signature indicates a return value of `void` (neither value nor promise). +Again, none of the examples above have us waiting for our callback to complete. In fact, our callback’s signature indicates a return value of `void` (neither value nor promise). ### Using `invoke` to handle async actions -But what if we really do care about the result of our callback? If we use a callback to make an async call to a server then we would surely be interested in the result of that call. In that case, we could use a machine's `invoke` feature to handle the async call properly. +But what if we really do care about the result of our callback? If we use a callback to make an async call to a server then we would surely be interested in the result of that call. In that case, we could use a machine’s `invoke` feature to handle the async call properly. ```ts export const toggleMachine = { @@ -299,7 +299,7 @@ const [state, send] = useMachine( ); ``` -Regardless of whether `doToggle` succeeds or fails, we will transition the machine to the correct state as defined in the machine's config. +Regardless of whether `doToggle` succeeds or fails, we will transition the machine to the correct state as defined in the machine’s config. ### The good and the bad @@ -307,9 +307,9 @@ Regardless of whether `doToggle` succeeds or fails, we will transition the machi We now have a way to tap into our hook either to be notified of state changes or to directly effect change (or delegate responsibility elsewhere). -In our examples, the component (and its authors) still don't need to know much about the workings of XState which depending on the knowledge and makeup of a team, could be an advantage. +In our examples, the component (and its authors) still don’t need to know much about the workings of XState which depending on the knowledge and makeup of a team, could be an advantage. -As long as the hook connects the callback param to the machine's actions or invokes, the component and even its effects should stay in sync with the machine's state. +As long as the hook connects the callback param to the machine’s actions or invokes, the component and even its effects should stay in sync with the machine’s state. #### Drawbacks @@ -326,4 +326,4 @@ However, we can remind ourselves as to what the machine and our hook still do fo ### Conclusion -Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this pattern or other similar patterns then I'd love to hear about your experiences! +Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this pattern or other similar patterns then I’d love to hear about your experiences! From 8c93ec9eeb177d565b6138236dd75e8b2c786942 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:18:01 -0400 Subject: [PATCH 06/32] Update code samples --- ...ssing-callback-params-to-machine-hooks.mdx | 43 +++++++++++++------ 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 6b24caba..157b8ff0 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -95,7 +95,7 @@ By abstracting some of this implementation, your hook can remain more generic an In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, we can register an `onToggle` action. ```ts -export const toggleMachine = { +const toggleMachine = createMachine({ id: "toggle", initial: "inactive", states: { @@ -118,7 +118,7 @@ export const toggleMachine = { }, }, }, -}; +}); ``` According to the [docs](https://xstate.js.org/docs/guides/actions.html#api), actions are (paraphrasing): @@ -132,20 +132,35 @@ According to the [docs](https://xstate.js.org/docs/guides/actions.html#api), act In our [options object](https://xstate.js.org/docs/packages/xstate-react/#usemachine-machine-options), the second argument to `useMachine`, we can define the implementation of our `notifyOnToggle` action. In our case, this is really a call to the `onToggle` callback with a boolean arg indicating our `isActive` status. ```ts -const [state, send] = useMachine( - () => - createMachine({ - ...toggleMachine, - initial: "inactive", - }), - { - actions: { - notifyOnToggle: (context, event, meta) => { - onToggle?.(meta.state.matches("active")); +const toggleMachine = createMachine({ + id: "toggle", + initial: "inactive", + states: { + inactive: { + on: { + TOGGLE: { + target: "active", + }, }, }, - } -); + active: { + on: { + TOGGLE: { + actions: ["notifyOnToggle"], + target: "inactive", + }, + }, + }, + }, +}); + +const [state, send] = useMachine(toggleMachine, { + actions: { + notifyOnToggle: (context, event, meta) => { + onToggle?.(meta.state.matches("active")); + }, + }, +}); ``` Things could get even more interesting if we were to modify the signature of `onToggle` and pass it other values from `context` or from `event`. From c49922c2a279576c88cc900b7064a38da154836a Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:21:48 -0400 Subject: [PATCH 07/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 32e817fe..b8a752be 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -22,7 +22,7 @@ publishedAt: 2022-9-12 Are you a React developer using [XState](https://xstate.js.org/) to model your application logic? In a previous post I wrote about how to [create custom XState machine hooks for use in a React application](https://stately.ai/blog/just-use-hooks-xstate-in-react-components) and, as an example, I referenced a basic implementation of [a `useToggleMachine` hook](https://codesandbox.io/s/usetogglemachine-example-1-lazy-machine-8zcbvs?file=/src/Toggler.tsx). In this post we’ll take the next step and explore the idea of **passing callback functions as params** to our custom machine hooks. -### What does passing callback params look like? +## What does passing callback params look like? In our previous example we passed in a `initialActive` boolean parameter to the `useToggleMachine` hook. From 931fb07df03bd421e56030589a076d02d319d6da Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:22:04 -0400 Subject: [PATCH 08/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index b8a752be..18f1011c 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -64,7 +64,7 @@ export const useToggleMachine = ( The callback’s signature also includes an `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we’re interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine’s current state. -### Why pass callbacks? +## Why pass callbacks? Part of a hook’s purpose is encapsulation. If your state machine and hook handle everything internally then you don’t need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let’s looks at a few of these. From 7be71a01eeeff25b5f17cb51d85aad02a7529bfa Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:22:13 -0400 Subject: [PATCH 09/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 18f1011c..27f4943b 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -68,7 +68,7 @@ The callback’s signature also includes an `isActive` boolean argument. This fl Part of a hook’s purpose is encapsulation. If your state machine and hook handle everything internally then you don’t need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let’s looks at a few of these. -#### Getting notified about state machine changes +### Getting notified about state machine changes We already get an `isActive` boolean flag from the `useToggleMachine` hook so the component can conditionally render a representation of the toggle state. From 757c230d33f657a2c29f01674c241fdbd4931432 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:22:21 -0400 Subject: [PATCH 10/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 27f4943b..029fecf4 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -78,7 +78,7 @@ const [isActive, toggle] = useToggleMachine(); However, using a callback alerts the component of a state changes the moment it happens, as soon as a transition completes. This saves the component from needing to compare the current value of `isActive` to the previous value in order to respond to a change. -#### Handling async server calls or external API calls +### Handling async server calls or external API calls We might choose to handle async calls to a server outside of our machine hook, possibly using an async hook like `useQuery` or `useMutation` from libraries like [SWR](https://swr.vercel.app/), [React Query](https://react-query.tanstack.com/), or [Apollo Client](https://www.apollographql.com/docs/react/). In this case, we could pass a callback to the machine hook that should be called when its machine enters a particular state. From 4248af206b5575846b8dcef5a69c825a4d464453 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:23:17 -0400 Subject: [PATCH 11/32] Remove possessive "hook's" Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 029fecf4..cf9bfc89 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -84,7 +84,7 @@ We might choose to handle async calls to a server outside of our machine hook, p #### Error handling, logging, and debugging -Receiving notification when your callback is invoked lets you handle errors and other events. We could show a toast, log to an external service, or temporarily debug our machine without digging into the code of the hook or the machine itself. +Receiving notification when your callback is invoked lets you handle errors and other events. We could show a toast, log to an external service, or temporarily debug our machine without digging into the hook’s code or the machine itself. #### Resusability From 1f887e1416c252d72c63dc7fbce62fe799e7a6b4 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:23:53 -0400 Subject: [PATCH 12/32] Add comma Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index cf9bfc89..f5ca885b 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -238,7 +238,7 @@ Again, none of the examples above have us waiting for our callback to complete. ### Using `invoke` to handle async actions -But what if we really do care about the result of our callback? If we use a callback to make an async call to a server then we would surely be interested in the result of that call. In that case, we could use a machine’s `invoke` feature to handle the async call properly. +But what if we really do care about the result of our callback? If we use a callback to make an async call to a server, then we would surely be interested in the result of that call. In that case, we could use a machine’s `invoke` feature to handle the async call properly. ```ts export const toggleMachine = { From 207fb8621433d1ba40c4dd467c519af28c70897d Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:25:47 -0400 Subject: [PATCH 13/32] Use single preposition instead of preposition phrase Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index f5ca885b..8ff070f9 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -317,7 +317,7 @@ Adding callback params to your custom machine hook does increase the surface are Your component, no longer only a dumb renderer of machine state, is now a bit more involved in handling or delegating effect management. -However, we can remind ourselves as to what the machine and our hook still do for us: +However, we can remind ourselves of what the machine and our hook still do for us: - Establishes the possible states and defines transitions between states - Manages how and when those state transitions happen From 528443eff50022525a54a5198a29404fe03fb0e9 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:26:10 -0400 Subject: [PATCH 14/32] Hyphenate outward-facing Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 8ff070f9..af838ea5 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -322,7 +322,7 @@ However, we can remind ourselves of what the machine and our hook still do for u - Establishes the possible states and defines transitions between states - Manages how and when those state transitions happen - Handles the change of our context values -- Allows us to map our callbacks and outward facing effects to the predictable mechanisms of the machine. +- Allows us to map our callbacks and outward-facing effects to the predictable mechanisms of the machine. ### Conclusion From 184dbff4c4eb86e05aacb7043dc2869e7edbd057 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:26:55 -0400 Subject: [PATCH 15/32] Reduce redundant use of "pattern", add comma Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index af838ea5..548b55a1 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -326,4 +326,4 @@ However, we can remind ourselves of what the machine and our hook still do for u ### Conclusion -Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this pattern or other similar patterns then I’d love to hear about your experiences! +Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this or other similar patterns, I’d love to hear about your experiences! From 10ba1892ca4d64357f1b6b2ae76a73c57e8702d2 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:28:05 -0400 Subject: [PATCH 16/32] Delete Introduction heading --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 -- 1 file changed, 2 deletions(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 157b8ff0..9c140bdc 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -18,8 +18,6 @@ excerpt: "" publishedAt: 2022-9-12 --- -### Introduction - Are you a React developer using [XState](https://xstate.js.org/) to model your application logic? In a previous post I wrote about how to [create custom XState machine hooks for use in a React application](https://stately.ai/blog/just-use-hooks-xstate-in-react-components) and, as an example, I referenced a basic implementation of [a `useToggleMachine` hook](https://codesandbox.io/s/usetogglemachine-example-1-lazy-machine-8zcbvs?file=/src/Toggler.tsx). In this post we'll take the next step and explore the idea of **passing callback functions as params** to our custom machine hooks. ### What does passing callback params look like? From 930591a9eb63c1f692f8ea6c6eda7bbd4f62be4a Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:30:34 -0400 Subject: [PATCH 17/32] Add comma Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 9976574c..e765485b 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -22,7 +22,7 @@ Are you a React developer using [XState](https://xstate.js.org/) to model your a ## What does passing callback params look like? -In our previous example we passed in a `initialActive` boolean parameter to the `useToggleMachine` hook. +In our previous example, we passed in a `initialActive` boolean parameter to the `useToggleMachine` hook. ```ts // Component From 4cd2f185e04f958dfc15463335461038f883f0d3 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:31:14 -0400 Subject: [PATCH 18/32] Fix typo and specify direct object Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index e765485b..b6d30a18 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -64,7 +64,7 @@ The callback’s signature also includes an `isActive` boolean argument. This fl ## Why pass callbacks? -Part of a hook’s purpose is encapsulation. If your state machine and hook handle everything internally then you don’t need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let’s looks at a few of these. +Part of a hook’s purpose is encapsulation. If your state machine and hook handle everything internally, then you don’t need to pass in extra parameters. However, there may be cases where you may want to have the component define or handle some of this implementation outside of the machine hook. Let’s look at a few of these cases. ### Getting notified about state machine changes From b6b71db414474231ac41df97610c812d8b70bca4 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:31:28 -0400 Subject: [PATCH 19/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index b6d30a18..65316a5f 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -80,7 +80,7 @@ However, using a callback alerts the component of a state changes the moment it We might choose to handle async calls to a server outside of our machine hook, possibly using an async hook like `useQuery` or `useMutation` from libraries like [SWR](https://swr.vercel.app/), [React Query](https://react-query.tanstack.com/), or [Apollo Client](https://www.apollographql.com/docs/react/). In this case, we could pass a callback to the machine hook that should be called when its machine enters a particular state. -#### Error handling, logging, and debugging +### Error handling, logging, and debugging Receiving notification when your callback is invoked lets you handle errors and other events. We could show a toast, log to an external service, or temporarily debug our machine without digging into the hook’s code or the machine itself. From a999eb3bafebc7fd2c829e21775c8f9cf888851a Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:31:38 -0400 Subject: [PATCH 20/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 65316a5f..26fe41e6 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -84,7 +84,7 @@ We might choose to handle async calls to a server outside of our machine hook, p Receiving notification when your callback is invoked lets you handle errors and other events. We could show a toast, log to an external service, or temporarily debug our machine without digging into the hook’s code or the machine itself. -#### Resusability +### Reusability By abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You’ll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. From d3843c014815bed3d7f46b9d19b6ae11666f22b7 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:31:48 -0400 Subject: [PATCH 21/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 26fe41e6..314e0d3f 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -88,7 +88,7 @@ Receiving notification when your callback is invoked lets you handle errors and By abstracting some of this implementation, your hook can remain more generic and reusable which, along with encapsulation, is also one of the purposes of using a hook. You’ll have to find the right balance of responsibilities between the machine hook, the component, and any other hooks you may use. -### Adding actions to the machine +## Adding actions to the machine In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, we can register an `onToggle` action. From 89dffc93fa87d66daa2c2dfdcc40928b18c18fda Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:31:57 -0400 Subject: [PATCH 22/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 314e0d3f..10c952ea 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -125,7 +125,7 @@ According to the [docs](https://xstate.js.org/docs/guides/actions.html#api), act 2. Treated as fire and forget. 3. Not supposed to directly impact the system, should they fail. -### Wiring up the callback param from actions +## Wiring up the callback param from actions In our [options object](https://xstate.js.org/docs/packages/xstate-react/#usemachine-machine-options), the second argument to `useMachine`, we can define the implementation of our `notifyOnToggle` action. In our case, this is really a call to the `onToggle` callback with a boolean arg indicating our `isActive` status. From adbbaf1241d15a60440391d314e0042c6d32ee58 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:32:06 -0400 Subject: [PATCH 23/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 10c952ea..1849cc27 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -249,7 +249,7 @@ const [state, send] = useMachine( Again, none of the examples above have us waiting for our callback to complete. In fact, our callback’s signature indicates a return value of `void` (neither value nor promise). -### Using `invoke` to handle async actions +## Using `invoke` to handle async actions But what if we really do care about the result of our callback? If we use a callback to make an async call to a server, then we would surely be interested in the result of that call. In that case, we could use a machine’s `invoke` feature to handle the async call properly. From 2937a858625f4b24c6c854b53465308f51185b31 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:32:18 -0400 Subject: [PATCH 24/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 1849cc27..64480f07 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -314,7 +314,7 @@ const [state, send] = useMachine( Regardless of whether `doToggle` succeeds or fails, we will transition the machine to the correct state as defined in the machine’s config. -### The good and the bad +## The good and the bad #### Benefits From 86fca2ae3a18eccf1f25f4400e301a51afd10dc4 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:32:23 -0400 Subject: [PATCH 25/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 64480f07..9652c0b0 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -316,7 +316,7 @@ Regardless of whether `doToggle` succeeds or fails, we will transition the machi ## The good and the bad -#### Benefits +### Benefits We now have a way to tap into our hook either to be notified of state changes or to directly effect change (or delegate responsibility elsewhere). From 62a1c46199c69b3ed633855a5d19437e7a122190 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:32:53 -0400 Subject: [PATCH 26/32] Improve wording Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 9652c0b0..6988bace 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -320,7 +320,7 @@ Regardless of whether `doToggle` succeeds or fails, we will transition the machi We now have a way to tap into our hook either to be notified of state changes or to directly effect change (or delegate responsibility elsewhere). -In our examples, the component (and its authors) still don’t need to know much about the workings of XState which depending on the knowledge and makeup of a team, could be an advantage. +In our examples, the component (and its authors) still don’t need to know much about the workings of XState which depending on a team’s knowledge and makeup, could be an advantage. As long as the hook connects the callback param to the machine’s actions or invokes, the component and even its effects should stay in sync with the machine’s state. From 71f45da1d52bb8b8664d423e91278245074e0aba Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:33:00 -0400 Subject: [PATCH 27/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 6988bace..5fc7c6fa 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -324,7 +324,7 @@ In our examples, the component (and its authors) still don’t need to know much As long as the hook connects the callback param to the machine’s actions or invokes, the component and even its effects should stay in sync with the machine’s state. -#### Drawbacks +### Drawbacks Adding callback params to your custom machine hook does increase the surface area of its API and there are are tradeoffs in complexity and duties. From 1daa1880c468bb38fd5dc93a7b2b1ac9ff3f9771 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:33:23 -0400 Subject: [PATCH 28/32] Fix duplicate word and add comma Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 5fc7c6fa..61b338c0 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -326,7 +326,7 @@ As long as the hook connects the callback param to the machine’s actions or in ### Drawbacks -Adding callback params to your custom machine hook does increase the surface area of its API and there are are tradeoffs in complexity and duties. +Adding callback params to your custom machine hook does increase the surface area of its API, and there are tradeoffs in complexity and duties. Your component, no longer only a dumb renderer of machine state, is now a bit more involved in handling or delegating effect management. From 62a096ce1beae9db41cfde504b82cf4c6995c704 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:33:30 -0400 Subject: [PATCH 29/32] Reducing heading level by 1 Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 61b338c0..70e80a0e 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -337,6 +337,6 @@ However, we can remind ourselves of what the machine and our hook still do for u - Handles the change of our context values - Allows us to map our callbacks and outward-facing effects to the predictable mechanisms of the machine. -### Conclusion +## Conclusion Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this or other similar patterns, I’d love to hear about your experiences! From e77b0aa329513436dbf0f4972a8550aaee0413b5 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Thu, 15 Sep 2022 21:34:19 -0400 Subject: [PATCH 30/32] Simplify wording Co-authored-by: Laura Kalbag --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index 70e80a0e..dc8654cf 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -60,7 +60,7 @@ export const useToggleMachine = ( }; ``` -The callback’s signature also includes an `isActive` boolean argument. This flag is similar to the boolean returned from the hook but here we’re interested in handling the state change as its own event as opposed to tracking the ongoing value of the machine’s current state. +The callback’s signature also includes an `isActive` boolean argument. This flag is similar to the boolean returned from the hook, but here we’re interested in handling the state change as its own event instead of tracking the ongoing value of the machine’s current state. ## Why pass callbacks? From 05fb3cd5247d9d18b03c9347294ee1b466dd2921 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Mon, 19 Sep 2022 16:08:09 -0400 Subject: [PATCH 31/32] Edit examples for accuracy and best practices --- ...ssing-callback-params-to-machine-hooks.mdx | 167 ++++++++++-------- 1 file changed, 98 insertions(+), 69 deletions(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index dc8654cf..a29442b8 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -90,33 +90,34 @@ By abstracting some of this implementation, your hook can remain more generic an ## Adding actions to the machine -In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, we can register an `onToggle` action. +In XState, the way to handle effects is by using [`actions`](https://xstate.js.org/docs/guides/actions.html#api). When responding to the TOGGLE event in our `toggleMachine` example, we can register an `notifyOnToggle` action. ```ts -const toggleMachine = createMachine({ - id: "toggle", - initial: "inactive", - states: { - inactive: { - on: { - TOGGLE: { - // Define an array of actions to be performed when the TOGGLE event is received - actions: ["notifyOnToggle"], - target: "active", +const createToggleMachine = (initialActive: boolean) => + (toggleMachine = createMachine({ + id: "toggle", + initial: initialActive ? "active" : "inactive", + states: { + inactive: { + on: { + TOGGLE: { + // Define an array of actions to be performed when the TOGGLE event is received + actions: ["notifyOnToggle"], + target: "active", + }, }, }, - }, - active: { - on: { - TOGGLE: { - // Same for this state. - actions: ["notifyOnToggle"], - target: "inactive", + active: { + on: { + TOGGLE: { + // Same for this state. + actions: ["notifyOnToggle"], + target: "inactive", + }, }, }, }, - }, -}); + })); ``` According to the [docs](https://xstate.js.org/docs/guides/actions.html#api), actions are (paraphrasing): @@ -130,61 +131,98 @@ According to the [docs](https://xstate.js.org/docs/guides/actions.html#api), act In our [options object](https://xstate.js.org/docs/packages/xstate-react/#usemachine-machine-options), the second argument to `useMachine`, we can define the implementation of our `notifyOnToggle` action. In our case, this is really a call to the `onToggle` callback with a boolean arg indicating our `isActive` status. ```ts -const toggleMachine = createMachine({ - id: "toggle", - initial: "inactive", - states: { - inactive: { - on: { - TOGGLE: { - target: "active", +const createToggleMachine = (initialActive: boolean) => + createMachine({ + id: "toggle", + initial: initialActive ? "active" : "inactive", + states: { + inactive: { + on: { + TOGGLE: { + actions: ["notifyOnToggle"], + target: "active", + }, }, }, - }, - active: { - on: { - TOGGLE: { - actions: ["notifyOnToggle"], - target: "inactive", + active: { + on: { + TOGGLE: { + actions: ["notifyOnToggle"], + target: "inactive", + }, }, }, }, - }, -}); + }); -const [state, send] = useMachine(toggleMachine, { +const [state, send] = useMachine(createToggleMachine(initialActive), { actions: { - notifyOnToggle: (context, event, meta) => { - onToggle?.(meta.state.matches("active")); + notifyOnToggle: (context, event) => { + // TODO: Check for the presence of the onToggle callback + // and pass it the current "active" status. + onToggle?.(/* true or false */); }, }, }); ``` +What about that last part, how do we know whether the toggle is active or inactive in order to pass it to the callback? One way is to use a "parameterized action" that can be defined in the statechart config. Instead of a `notifyOnToggle` string we can specify an object, including a `type` and custom `active` property. Now we can explicitly pass in a boolean value for `active`, per state. + +```ts +states: { + inactive: { + on: { + TOGGLE: { + actions: [{ type: "notifyOnToggle", active: true }], + target: "active", + }, + }, + }, + active: { + on: { + TOGGLE: { + actions: [{ type: "notifyOnToggle", active: false }], + target: "inactive", + }, + }, + }, +}, +``` + +That object will be received in our action implementation, attached to a third "meta" argument. + +```ts +actions: { + notifyOnToggle: (context, event, meta) => { + onToggle?.(meta.action.active); + }, +}, +``` + Things could get even more interesting if we were to modify the signature of `onToggle` and pass it other values from `context` or from `event`. ```ts // Example onToggle({ - isActive: meta.state.matches("active"), + isActive: meta.action.active, numOfToggles: context.numChange, }); ``` -If the signature of `onToggle` matches that of an XState action, you could even pass the entire action implementation in as the callback itself. +If the signature of `onToggle` matches that of an XState action, you could even assign the callback param itself as the value of the action for greater external access (assuming `onToggle` is required). ```ts { actions: { - // onToggle will be called with context, event, and meta args. + // The callback param, onToggle, will be called with context, event, and meta args. notifyOnToggle: onToggle, }, } ``` -To be even more explicit, we could pass in multiple callback params and map them to discrete actions. +Another way to distinguish between state changes (and avoid the boolean flag and parameterized actions) would be to pass in multiple callback params, mapping those to discrete actions. -When responding to the `TOGGLE` event in our `toggleMachine` example, we can add an `activate` action to the `inactive` state and also a `deactivate` action to the `active` state. +When responding to the `TOGGLE` event in our `toggleMachine` example, we can add an `notifyActivation` action to the `inactive` state and also a `notifyDeactivation` action to the `active` state. ```ts export const toggleMachine = { @@ -194,8 +232,7 @@ export const toggleMachine = { inactive: { on: { TOGGLE: { - // Define an array of actions to be performed when the TOGGLE event is received - actions: ["activate"], + actions: ["notifyActivation"], target: "active", }, }, @@ -203,8 +240,7 @@ export const toggleMachine = { active: { on: { TOGGLE: { - // Same for this state. - actions: ["deactivate"], + actions: ["notifyDeactivation"], target: "inactive", }, }, @@ -213,19 +249,19 @@ export const toggleMachine = { }; ``` -We could then pass in two callbacks to the hook. +We could then pass in two callbacks to the hook (they can still be optional). ```ts export const useToggleMachine = ( initialActive: boolean, - onActivate: () => void - onDeactivate: () => void + onActivation: () => void + onDeactivation: () => void ) => { // ... }; ``` -We then call the callbacks from their respective actions without passing arguments indicating current state. +We then call the callbacks from their respective actions without any arguments. ```ts const [state, send] = useMachine( @@ -236,11 +272,11 @@ const [state, send] = useMachine( }), { actions: { - activate: (context, event) => { - onActivation?.(); + notifyActivation: (context, event) => { + onActivation(); }, - deactivate: (context, event) => { - onDeactivation?.(); + notifyDeactivation: (context, event) => { + onDeactivation(); }, }, } @@ -298,18 +334,11 @@ export const useToggleMachine = ( Here is our `togglingActive` definition under `services` in our options object, passed as the second argument to `useMachine`. The source of the `toggleActive` invoke is our async callback function. ```ts -const [state, send] = useMachine( - () => - createMachine({ - ...toggleMachine, - initial: "inactive", - }), - { - invoke: { - togglingActive: doToggle, - }, - } -); +const [state, send] = useMachine(() => createToggleMachine(initialActive), { + invoke: { + togglingActive: doToggle, + }, +}); ``` Regardless of whether `doToggle` succeeds or fails, we will transition the machine to the correct state as defined in the machine’s config. @@ -339,4 +368,4 @@ However, we can remind ourselves of what the machine and our hook still do for u ## Conclusion -Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks. If you have used this or other similar patterns, I’d love to hear about your experiences! +Hopefully, this post has given you some ideas on why and how to use callback params in your custom machine hooks so you can find the right balance between having your custom machine hook or your component handle functionality. If you have used this or other similar patterns, I’d love to hear about your experiences! From c9e2c8a5db8589cf35f127f380c70aff81df5ff6 Mon Sep 17 00:00:00 2001 From: Kevin Maes Date: Mon, 19 Sep 2022 16:11:07 -0400 Subject: [PATCH 32/32] Update date --- .../2022-9-12-passing-callback-params-to-machine-hooks.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx index a29442b8..22c27a84 100644 --- a/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx +++ b/content/posts/2022-9-12-passing-callback-params-to-machine-hooks.mdx @@ -15,7 +15,7 @@ author: - - Kevin Maes originalURL: "" excerpt: "" -publishedAt: 2022-9-12 +publishedAt: 2022-9-20 --- Are you a React developer using [XState](https://xstate.js.org/) to model your application logic? In a previous post I wrote about how to [create custom XState machine hooks for use in a React application](https://stately.ai/blog/just-use-hooks-xstate-in-react-components) and, as an example, I referenced a basic implementation of [a `useToggleMachine` hook](https://codesandbox.io/s/usetogglemachine-example-1-lazy-machine-8zcbvs?file=/src/Toggler.tsx). In this post we'll take the next step and explore the idea of **passing callback functions as params** to our custom machine hooks.