New model utility function #1579
NicholasBoll
started this conversation in
Ideas
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
-
Models and model hooks
Let's clear up some terminology:
state
andevents
Models have a convention around events that add "guard" and "callback" configuration:
Code Sandbox
To decrease the boilerplate and possible errors by manually implementing guards and callbacks in event code, we created a series of helper functions and helper types. It gets long and messy, but here's an example:
https://github.com/Workday/canvas-kit/blob/c872a9a078a5893eb9149ccf5599d91ed2f24d01/modules/react/disclosure/lib/useDisclosureModel.tsx
From the code sample, we can see we have a lot of types and values that need to be created:
DisclosureState
: type forstate
DisclosureEvents
: type forevents
DisclosureModel
: type that is returned fromuseDisclosureModel
disclosureEventMap
: value that captures a mapping of events to guards and callbacks. Also used in some typesBaseDisclosureModelConfig
: A base config type that doesn't include guards or callbacks. This config contains things like default values of state propertiesDisclosureModelConfig
: CombinesBaseDisclosureModelConfig
and guard and callback config via aToModelConfig
utility type that uses the event map to createuseDisclosureModel
: The model hookuseEventMap
: Used by the model hook to automatically create guard and callbacks so our events can focus only on informing React about state changes.This is a lot of boilerplate Typescript code. The event map is needed because we supported Typescript 3.8 which did not yet have Template Literal Types which are necessary to automatically create guard and callback types.
Typescript 4.1 and Template Literal Types
Starting with v7, Canvas Kit supports Typescript 4.1 which supports Template Literal Types. This means we can take an interface and return a mapped type with remapped keys and values. Here's an example:
Here's the code in a Typescript playground example.
This new feature allows us to get rid of the event map and work to reduce all the boilerplate required.
Introducing
createModelHook
This new utility function allows to focus on the important pieces of our models: the config, events, and state:
Here's the full disclosure model hook code
createModelHook
takes config in the first function. There are 2 main config types:defaultConfig
: This is config that has default values. The function will both assign a default value if the user hasn't provided it and also extract the type.requiredConfig
: This config is required to be provided when the hook is called. There is no default. The value here will not be used, but the type and the key name will be usedThere's also an optional config with no default. Here's an example of all config:
getElemProps
The
requiredConfig
is definitely weird for Typescript users, but thedefaultConfig
andrequiredConfig
keys are used to filter out config from props so all that is left iselemProps
. The utility function returns a function calledgetElemProps
that will take in all props and only return non-config props. Let's look at an example:The
createModelHook
function used the keys from thedefaultConfig
andrequiredConfig
to figure out which props passed were config and leaves the rest to be safely passed to a JSX element.Context
The
createModelHook
also attaches aContext
property. Most likely you don't need to use this directly. It is used by thecreateContainer
andcreateSubcomponent
functions to handle models in compound components.Usage:
defaultConfig
andrequiredConfig
The
createModelHook
re-attaches thedefaultConfig
andrequiredConfig
you passed to it when you created a model hook. It does this so you can easily extend the model to create new ones. Let's extend ouruseMyModel
from above to show you what we mean:contextOverride
The
createModelHook
will create a new React Context usingReact.createContext
that is used to pass the model around the component tree. We may want to use the same context as the model we're extending. ThecontextOverride
will allow us to do this. This is useful when you want your model to work on existing compound components. Maybe you're modifying the model slightly, but still using the model on existing components.Now you can use
useExtendMyModel
in the same places thatuseMyModel
would work since the context is the same.Note: When using
contextOverride
, your model should have the same shape as the model you're extending. You can add new state or events, but you cannot remove or change the type signatures of existing state or events. Doing so will result in both type and runtime errors.Beta Was this translation helpful? Give feedback.
All reactions