Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save dominicstop/7d058d18126f6de490660f68348d0bac to your computer and use it in GitHub Desktop.

Select an option

Save dominicstop/7d058d18126f6de490660f68348d0bac to your computer and use it in GitHub Desktop.

Learning Tamagui Fundamentals

Metadata



  • Introduction

    • @tamagui/core - Docs: React style and design system library.
      • The style API (i.e. styled function) is similar to styled-components (css-in-js).
      • It is similar to react-native-web, in that it supports view and text RN components on the web (docs: "supports the full React Native API surface [in the web]").
      • The styling api supports: media queries (e.g. minWidth, maxHeight), pseudo selectors (e.g. css psuedo-classes like :hover and :focus states), container queries (e.g. apply styles to an element based on the size of its container), and other selectors.

  • Configuration: Custom themes, tokens, shorthands, and media queries.

    • It all starts with creating a tamagui config: tamagui.config.ts.
      • The createTamagui function is declared in createTamagui.ts.
      • The first and only parameter named configIn, and accepts an object of type CreateTamaguiProps.
        • CreateTamaguiProps seems to define the configs accepted by the createTamagui function (i.e. because it matches the properties mentioned in docs, e.g. shorthands, media, token etc).
        • There are some properties that are remapped from a type called: GenericTamaguiConfig (i.e. media, fonts, and tokens).
        • There is a optional property called unset that accepts values of type: BaseStyleProps.
        • There is a optional property called animations that accepts values of type: animations?: AnimationDriver<any>.

      • createTamagui returns a type of InferTamaguiConfig<CreateTamaguiProps>.
        • InferTamaguiConfig accepts a generic parameter named Conf that extends ConfProps.
        • The ConfProps type accepts generic parameters "A to I", and remaps them to become a value in an object.
          • E.g. A remaps to tokens?: A, B remaps to themes?: B, and so on.
          • So ConfProps is an object w/ the ff. properties: tokens, themes, shorthands, media, animations, fonts, onlyAllowShorthands, defaultFont, and settings.
          • These look like the "shape" for a tamagui config. But i don't know why it's defined like this yet.
          • One interesting thing is that the value for animations property can be either AnimationDriver<E> or undefined
            • This is done so with a conditional type (i.e. in the code, it is declared like this: E extends AnimationConfig ? AnimationDriver<E> : undefined).
            • In others words, if E has the shape of type: AnimationConfig, then the type should be: AnimationDriver<AnimationConfig>, otherwise it'll be undefined.
            • In the docs, it is mentioned that tamagui supports different "animation" API's (e.g. animated, moti, etc)., so this type could be related to that.
            • The type AnimationConfig is a record that accepts any value.
            • See #AnimationDriver bullet point for more info on AnimationDriver.

        • InferTamaguiConfig accepts a generic parameter Conf, and is used in a conditional type, i.e. InferTamaguiConfig<Conf> = Conf extends ConfProps<...>; i.e. if the shape of Conf matches ConfProps then the type will be: TamaguiInternalConfig<...>, otherwise it's unknown.
          • As discussed in the prev. bullet point, ConfProps accepts generic parameters "A through I".
          • Usage of type ConfProps in InferTamaguiConfig, prefixes the generic parameter declarations "A through I" w/ the infer  keyword (hence the name InferTamaguiConfig).
          • Generic parameters "A through I" from ConfProps are then passed to TamaguiInternalConfig.
          • But what does the infer keyword do in this context? is it just to foce type inference?
          • So the return type InferTamaguiConfig<CreateTamaguiProps> is really just: TamaguiInternalConfig?

      • The createTamagui function at first glance looks like a parser.
      • # There is a call to a function called createVariables which receives the argument configIn.tokens which has type of: tokens?: GenericTokens | undefined.
        • The 1st parameter tokens from createVariables accepts a type of: DeepTokenObject
          • The usage of "deep" in the type name seems to imply that the object is a recursive type, i.e. in the sense that the value for a property in DeepTokenObject can be either itself, or string | number.
          • As such, given that there are only 2 outcomes for the properties's value type, no matter how "deep" the object is (e.g. foo, foo.bar, foo.bar.baz), the final nested object will always have a property value of type: string | number.
          • This kinda makes sense since when using tokens (e.g. in styled) you pass a "keypath" string, e.g. $foo.bar.baz, and baz can either be a string or number.

        • This function returns an object of type: DeepVariableObject<DeepTokenObject>.
          • As such this function essentially transforms (or possibly remaps) DeepTokenObject to DeepVariableObject.
          • For each property in DeepTokenObject, if it's value is of type: string | number, then it's a Variable<string | number>; otherwise if the value of the DeepTokenObject property is an object that has the shape of DeepTokenObject, then it's a DeepVariableObject<DeepTokenObject>.
          • Essentially like, DeepTokenObject, the use of "deep" in the naming of the type denotes that it is a recursive object; with a a property either having a value of Variable<string | number>, or itself.
          • As such, no matter how "deep" the object is, the final nested object will have property with a value type of Variable<string | number>.
          • So we can conclude, that this functions transforms DeepTokenObject to DeepVariableObject.

        • The other params are meant to be used by the recursive call to itself to pass more info, e.g. parentPath, and isFont.
        • Looking at the code, there seems to be a cache/"look up table" to store the prev. results (i.e. for optimization).
          • Interestingly all the code needed to map a particular "tokens" object to Variable object is: cache.set(res, true) + cache.has(tokens).
          • This implies that the tokens object is hashable somehow, and the "order" of properties (when hashed) is consistent across different object instances so long as it's "shape" and value is the same; i thought that JS doesn't guarantee the order of objects? Maybe it sort's them alphabetically? or uses some other mechanism.

        • So this chunk of code is traversing through a DeepVariableObject object (because it has a recursive call to itself here)
          • E.g. all roads lead to Variable<string | number>, no matter how "deep" the object is.
          • # The check for whether the current property is of Variable is via done via a function called isVariable(val) here; it just checks if the value passed is of type object, and has a property declared called isVar.
          • If the property value is already of type: Variable, then it gets added to res, otherwise createVariable is called first.

        • There is some code that prefixes the current key with $ (and assigns it to a variable named keyWithPrefix), and another that creates a key that is not prefixed with $ (which is assigned to variable key).
          • This suggests that current key string may or may not have a $ prefix, and this chunk of code "normalizes" things.

        • Code - Checks that the current property value is of Variable, then skips to next property.
        • Code - Some code related to the construction of a string value for variable called name; it is in kebab case "key path" of sorts (e.g. foo-bar-baz), representing the current object path?
          • I say of sorts because the key seems to be hashed via simpleHash(key) (which defined in packages/simple-hash).
          • As such it's more likely like: 1-2-3-ahdh374 or maybe even: jfwe32-efwse-2scwe.
          • I think this might be for the CSS name? Used to prevent class name collisions?

        • As mentioned earlier, there can be an invocation to createVariable at the end.
          • Hence the name createVariables, i.e. createVariables -> createVariable.
          • The first parameter named props, accepts VariableIn<string | number | Variable> (declared here).
          • VariableIn is a subset of object Variable; only containing the properties: key: string, name: string, val: string | number | Variable.
          • This type is possibly recursive since val can be of type Variable, and VariableIn is a subset of Variable.
          • createVariable returns a Variable<string | number | Variable> object.
            • The properties key and, val from the props param. is just copied over to the return object.
            • The property Variable.isVar is explicitly set to true in the object to be returned; probably used "brand" an object; this property is set here.
            • The value for Variable.name could just be a copy of props.name, or passed through simpleHash(name, 40).
            • Finally, the Variable.variable is a css variable string if on the web, otherwise it's just an empty string.
        • From this, createVariable + Variable type is used to create and represent CSS variables in the JS side.

      • Let's head back to the createTamagui function again; there seems to be logic that traverses through tokens (i.e. the value from the output of createVariables(configIn.tokens)).
        • Even though tokens is of type DeepVariableObject<GenericTokens> (with "deep" suggesting that it is of recursive type), the "for loop" used to traverse tokens only goes two levels deep; e.g. traversing from each category in the tokens object (which is a Variable), and then traversing through each item in the category object, and so on.
        • As such we have to assume that the value being extracted here is a value of: Variable<string | number>? Type inference says that it's any.
        • It seems that the leaf objects (i.e. the deepest object that is the shape of Variable<string | number>, e.g. for foo.bar.baz, baz is the leaf object) in tokens are plucked and stored in tokensParsed.
        • # tokensMerged on the other hand is similar to tokensParsed, but it contains duplicate properties (keys with and without the $ prefix); tokensMerged is passed setTokens and is probably cached as well.
          • The docs mentions that you can optionally obtain a token property key that is prefixed with or without $ via getTokens({prefixed: boolean}).
          • As such, this could be the reason why tokensMerged stores duplicate properties but i am not sure yet.
          • Continued in getTokens().

      • Code - Parsing the fonts config which gets assigned to fontsParsed; TBC.
      • # Code - Parsing the theme config, which gets assigned to themeConfig; TBC.
      • Code - All of the parsed data is used to construct a TamaguiInternalConfig object named config.
      • Code - The config object is then passed as an argument to other "setup-like" functions: i.e. configureMedia(config), setConfig(config), createdConfigs.set(config, true)
      • Code - There is an event emitter configListeners that notifies it's listeners that the config has been processed.
      • Code - Some debugging related-code; namely printing the config to the console, and creating a global Tamagui object if needed.
      • Code - config is then typed-erased, and returned so that InferTamaguiConfig<CreateTamaguiProps> is the inferred return type.

    • Let's go back to the docs; In order to create a tamagui config the ff. are defined: a "font config" (created via createFont({...})), a "token map" (created via createTokens), a "themes config", a "media queries" object (created via createMedia({...})), a "shorthands" substitution map.
      • Docs: theme: Define your design theme, which map to CSS properties.
      • Docs: media: Define reusable responsive media queries.
      • Docs: shorthands: Define any props you want to expand to style values, keys being the shorthand and values being the expanded style prop.

  • Tokens:

    • Docs: Use createTokens to generate variables in your theme and app; They are mapped to CSS variables at build time.
    • Font Tokens:
      • Clarification: "Tamagui core doesn’t do any matching but the way the higher level tamagui components are designed they try and use the properties when you use “size” for font "
      • Docs: Created with createFont; [Customize "styling" of font] based on each font family
        • E.g. custom defined size/lineHeight/weight by family; choose a specific "vertical rhythm" per font.
        • Docs: Note, you don't need to use numbered keys, you can use sm or tiny [so long as the keys are consistent].

      • There seems to be a concept of a "main" key, in the sense that across font category tokens, they must follow the same sets of keys defined in GenericFont.size.
        • Docs: The keys of size, lineHeight, weight, and letterSpacing are meant to match; Define the full set of keys on size, the rest can be a subset.
        • E.g. if the font "token key" = $small, it will use the matching values in the font categories; the size of the text will be the value defined in GenericFont.size['$small'], and the lineHeight of the text will be GenericFont.lineHeight['$small'], and so on.
        • Docs: Missing keys from partially defined objects will be filled in; At creation Tamagui fills in the missing keys with previous value, or the next one if no previous exists.

      • To use tokens, you pass a string prefixed with $ (e.g. "$small") to a "style" prop (e.g. <Text fontSize="$large">, <Text fontFamily="$mono"/>).
      • Non-Font Tokens:
        • These tokens categories are not specific to fonts, e.g. space, size, color, etc.

    • Tokens are kind of like a static KV-store, and is used to remap keys to concrete constant values (value substitution map?) in RN, but uses CSS variables on the web.
      • Clarification: "tokens never change. Defined only once".
      • E.g. tokens = {size: {none: 0, small: 3}} and <View radius="$small"/>, then <View radius={3}/>.
      • But also, since it is highly recommended that the "token keys" remain consistent across token categories (e.g. tokens['size'] = {none: 0, small: 3}, tokens['space'] = {none: 0, small: 4}, tokens['radius'] = {none: 0, small: 1}, tokens['zIndex'] = {none: 0, small: 1}, etc)., then it implies that all of these will be automatically substitute the values to the associated token value "all at once" given a token key.
      • E.g. if the token key is $small, then: size={3}, space={4}, radius={1}, and so on; Essentially, it's kind of like pattern matching + value substitution.
      • I don't know yet what API allows to do these, but i think i remember reading about it in "groups" and "themes".
      • The type explicitly declares the properties: size, color, space, radius, and zIndex; so these particular categories are reserved, and have special meaning.
      • The docs mentioned this: "Tamagui knows which tokens to use based on the style property you use"; as such, the reserved token categories must be mapped to certain style props (e.g. GenericToken.color mapping to View.styles.backgroundColor, or rather: View.backgroundColor because styles in in-lined as props).
      • As such, a token is a global static variable (that is JS representation of CSS variables), w/ special logic that allows for it to be used in the "tamagui config" itself (e.g. themes), but also in components style props (in which the token key is substituted with the concrete values, either via CSS variables or regular JS values at "compile" time?).

    • As prev. explored in the createTamagui function, tokens are created using createTokens.
      • createTokens({...}) -> createTamagui({tokens, ...}).
      • const tokens = createTokens({size: { small: 10 }).
      • The createTokens function accepts a parameter named tokens that is of type: CreateTokens, and returns an object of type MakeTokens<CreateTokens> defined here.
      • # Let's start w/ the type CreateTokens; it is a combination of {categoryKey: {tokenKey: Val}} + Record<'color' | 'space' | 'size' | 'radius', 'zIndex', Record<string, Val>>, where Val is just: number | string | Variable; so it's a map of maps.
        • The first part are the type def. for custom category tokens, while the 2nd part are special/reserved category tokens that have some logic that lets them be auto mapped to certain style props.
        • So the "lossy" type for this is: Record<string, Record<string, Val>> or Record<string, Record<number | string | Variable>>.

      • Next, let's take a look at MakeTokens<CreateTokens> (which is defined here):
        • It has the ff. comment: "verbose but gives us nice types...", "removes $ prefix allowing for defining either as $1: or 1: which is important because Chrome re-orders numerical-seeming keys :/".
        • The type is a bit complex, but from what i can gather (based on the comment), the type is declared this way for better TS inference + autocomplete.
        • A type named NormalizeTokens seems to be used to remove the $ prefix.
          • NormalizeTokens declares generic type A which is the type of the object (but based on this context, this is likely a "token category" map, e.g. size: {...}, color: {...}, etc or more succinctly: Record<string, VariableVal>).

        • This type uses conditional types and asks the question T  extends {...}
          • T generic param. is defined as T extends CreateTokens (jump here for more info).
      • This function seems to call and pass the tokens argument to createVariables and returns the type-erased result so that the return type will be MakeTokens<CreateTokens>.
      • Jump here for more info about createVariables, but basically this function returns an object of type: DeepVariableObject<DeepTokenObject>
      • This kinda makes sense since variables are created from tokens.
        • "tokens -> variables", i.e. tokens is the config for making variables, and variables are the "processed" version of tokens.
        • As such, under the hood, tokens are just variables (or maybe "tokens" is the public API, and "variables" is the internal representation).
        • In either case, i think the tamgui compiler will take all the defined variables, and use it to generate CSS but i haven't found the code yet that does this.
        • Docs: "Use createTokens to generate variables in your theme and app; They are mapped to CSS variables at build time".
      • This is also the reason why getTokens (discussed here) returns an object of type Variable.

    • # Docs: "you can access your tokens from anywhere using getTokens", e.g. getTokens().size.small or getTokens().size['$small'], and the value you receive will be of type Variable.
      • # getTokens() returns an object of type TokensMerged, which is defined as: TokensParsed & Tokens.
        • Let's start w/ Tokens types, which is just an alias defined as: TamaguiConfig['tokens']
        • Traverse Type: TamaguiConfig -> GenericTamaguiConfig -> GenericTokens -> CreateTokens.
        • So the Tokens type is really just CreateTokens type; Jump here for more details.
        • TokensParsed is similar to Tokens because it has same keys, i.e. it remaps Tokens to TokenPrefixed<Tokens[KeyInTokens]>); this type just adds a $ to the property key?
        • As such, the reason why this type is called TokensMerged because it contains the tokens + a copy that are prefixed with $ (e.g. $foo.bar and foo.bar); this type is used is used here.

      • # The tokens are retrieved by reading a file-scoped variable called conf, w/ it's type being defined as: TamaguiInternalConfig | null.
        • The conf variable looks like it contains the cached config returned by createTamagui (which is set here).

      • The docs mention helpers functions: "getVariable which will return the CSS variable on web, but raw value on native, and getVariableValue which always returns the raw value".
      • It looks like Variable is the JS representation of "CSS variables".

    • Subtopic: "Using tokens with components"
      • Docs: "Tokens are automatically applied to certain properties. For example, size tokens are applied to width and height. And of course radius to borderRadius".
      • Tokens are available in built-in tamagui components (e.g. Stack), as well as in components wrapped using styled.

    • Subtopic: Specific tokens
      • Specific tokens (i.e. custom user-defined tokens) can be created using createTokens, e.g. createTokens({customToken: {tokenKey: 12}}).
      • It can then be accessed via the "specific tokens" syntax (which involves the explicit declaration of the category name), e.g. <Stack height={$customToken.tokenKey}.

  • Themes:

    • Clarification: "Themes = css variables but you can change them contextually for other".
    • Docs - Note: Tokens are considered a "fallback" to themes, so any values you define in your theme will override the token.
      • Docs: "Color tokens as fallback values for themes" (i.e. tokens.color, the color token category).
      • Docs: "Color tokens are available as fallback values when you access a theme. So when you useTheme() and then access a value that isn't in the theme, it will check for a tokens.color with the matching name".
    • Tokens are variables, themes use tokens to "to create consistent, generic properties that you then typically use in shareable components"; however, themes should generally only deal with colors.


  • Styles: "The Tamagui superset of React Native styles"

    • Docs: Similar to "styled components", you can use styled() to create components that contain styles (e.g. derivatives of View and Text).
    • In tamagui, styles are individual props you can pass to the component (e.g. <View padding={10}/>).
    • # It seems that StackStyleBase is the root/base type for styles.
    • StackStyleBase type is an interface that is a union/combination of several objects: ViewStyle, TransformStyleProps, ExtraStyleProps, and OverrideNonStyledProps.
      • ViewStyle is imported from RN; although some properties are excluded via Omit (i.e. keyof OverrideNonStyledProps, and elevation).
      • TransformStyleProps is an object that contains 3d transform-related properties (e.g. x, y, rotateX, rotateY).
      • ExtraStyleProps is a pretty big object; a lot of the properties are marked web-only.
        • Some of the properties accept tokens: e.g. borderInlineColor?: ColorTokens, borderBlockWidth?: SpaceTokens | number, blockSize?: SizeTokens | number.
        • I think i saw this type somewhere in the docs (update: it's mentioned here).
        • A lot of the properties seems to be a remapped from an object type called Properties (e.g. contain?: Properties['contain']), and marked as web-only.
        • Ah, Properties is imported from csstype. Makes sense why they are all marked as web-only.
        • There are some animation related properties, e.g.: animation?: AnimationProp | null, animateOnly?: string[], animatePresence?: boolean.
        • There are some properties that are remapped from RN ViewStyle, e.g.: borderBlockStyle?: ViewStyle['borderStyle'], borderBlockStartStyle?: ViewStyle['borderStyle'], etc.
        • These seem to be the styles that are in-lined as props?

    • Subtopic - Parent Based Styling:
      • Docs: Tamagui has a variety of ways to style a child based on the "parent", a parent being one of: platform (e.g. $platform-ios), screen size (i.e. via user-defined media queries, e.g. $large), theme (e.g. $theme-light), or group (e.g. $group-header).

      • Subtopic - Parent Based Styling: Media Query
      • Subtopic - Parent Based Styling: Theme
      • Subtopic - Parent Based Styling: Platform
      • Subtopic - Parent Based Styling: Group
      • Subtopic - Parent Based Styling: Group Container
  • Media:

  • Animations:



  • Topic: createMedia
    • The createMedia function (defined in package "@tamagui/react-native-media-driver") defines a parameter named media which is a map defined as: Record<string, MediaQueryObject>, and also returns object of type: Record<string, MediaQueryObject>.
      • This is because it just returns the media parameter.

    • Inside the body of the function, it invokes setupMatchMedia(matchMedia).
    • The function setupMatchMedia uses platform specific code and is implemented in two files: helpers/matchMedia.ts (web), and helpers/matchMedia.native.ts (native),
      • On the web (matchMedia.ts), the setupMatchMedia function is no-op, and has the ff. comment: "no-op web",
      • On native (i.e. matchMedia.native.ts), the setupMatchMedia assigns the argument you pass to it (i.e. MatchMedia) to globalThis['matchMedia'] (the global context); effectively providing a shim for the web's matchMedia on native.
      • On native i.e. (helpers/matchMedia.native.ts), there is file level variable called matchMediaImpl.
        • The matchMediaImpl variable has a type of: MatchMedia; as such, this variable contains the native impl. of matchMedia.
        • The default value for matchMediaImpl is matchMediaFallback.
        • It looks like matchMediaFallback is a dummy/placeholder implementation.
        • On the dev build, invoking matchMediaFallback will result in a warning: "warning: matchMedia implementation is not provided".
        • The return value of matchMediaFallback (i.e. MediaQueryList) contains no-op implementations.
        • As the actual impl. will be provided later; this is because the file exports a setter function for matchMediaImpl named: setupMatchMedia.

    • Now let's discuss the matchMedia argument passed to setupMatchMedia (i.e. setupMatchMedia(matchMedia)).
      • The argument matchMedia is a function of type: MatchMedia.
        • The MatchMedia type is defined as: (query: string) => MediaQueryList.
        • MediaQueryList is an interface to an object that has functions (i.e. addListener, removeListener, match), and a property called matches.
        • This resembles the return value of the function window.matchMedia(mediaQueryString).
        • The class NativeMediaQueryList implements this.
        • Based on this, we can conclude that MatchMedia and MediaQueryList types is a mirror/shim of the matchMedia web API and it serves a proxy/bridge to have a common type definition between native and JS.

      • The matchMedia function uses platform specific code and is implemented in two files: matchMedia.ts (web), and matchMedia.native.ts (native).
      • On the web (i.e. matchMedia.ts), the values assigned to the matchMedia constant is: globalThis['matchMedia'],
        • This function is defined like this: function matchMedia(query: string): MediaQueryList; and is part of the browser/web api (mdn ref).
        • So it's an alias to the web's matchMedia API.

      • On native (i.e, matchMedia.native.ts), matchMedia is a function that instantiates a NativeMediaQueryList object, and passes query argument to it, then returns the instantiated object.
        • The NativeMediaQueryList class implements MediaQueryList, and implements all the needed methods + properties to conform to MediaQueryList, i.e. addListener, removeListener, match, and a property called matches.
        • The constructor for NativeMediaQueryList accepts query: string.
        • This class seems to use Dimensions module to implement the media query api.
        • The Listeners are defined as: (orientation: Orientation) => void.
        • There are calls to a function named matchQuery (i.e. in method matchQuery, and getter matches).

    • In conclusion, the matchMedia function passed to setupMatchMedia is the platform's impl. of the "match media" API, and setupMatchMedia makes sure that matchMedia is available in the global context.



  • # Topic -Type: AnimationDriver
    • The type AnimationDriver object accepts a generic parameter named A of type AnimationConfig (see prev. bullet point).

    • The generic param A is assigned as a value to a property named: animations, so this means this property accepts an AnimationConfig object.

    • There are two properties that are untyped: View, and Text (they are capitalized for some reason, but idk what is the intended meaning behind that; this could maybe mean that it accepts a react component?).

    • This object type declares "feature" flag properties: isReactNative, and supportsCSSVars. Likely used for some logic related to the animation driver (e.g. deciding what "animation driver" to use for a particular platform).

    • It has several properties that accepts functions, some of which are prefixed with use (which might denote that they accept hook functions).

    • The property useAnimations, accepts a type called UseAnimationHook.

      • This type is a function that takes a prop object as its only argument, and returns either null or an object that has a single optional property called style.
      • It is defined like so: style?: StackStyleBase | StackStyleBase[], which is defined here; Jump to this section for more details.
      • At first glance, it kinda looks like the styles prop from RN because it can optionally accept an array.
      • Also, in tamagui there are stack components, so they could be related to this type.



Learning Hooks (Reuse and Composition)



Project Structure and Code Style Analysis



How Do We Make Headless Components in Tamagui

  • Terminology: styled, unstyled, headless.

Let's start with Checkbox

  • The headless version of checkbox uses a pressable component as the "base/root" component.
    • Then it explicitly declares a checked state.
    • The HeadlessCheckbox uses forwardRef to forward the Pressable components ref.
    • Then it uses a hook called useCheckbox
      • The first parameter (props), accepts an argument of type CheckboxProps.
        • It basically accepts a union comprising of: ViewProps, PressableProps['onPress'], and a type called CheckboxExtraProps.
        • It's usage in HeadlessCheckbox, is that the useCheckbox hook receives HeadlessCheckbox.props.
        • The actual HeadlessCheckbox doesn't have any logic in it, mostly just UI stuff.
      • The second parameter is an array that de-structured to checked, and setChecked tuple. The type of checked is CheckedState, and the type of setChecked is React.Dispatch<React.SetStateAction<CheckedState>>.
        • What is React.Dispatch? The type signature for it is: type Dispatch<T> = (action: T) => void;
          • Dispatch takes an action as a parameter and returns nothing meaningful (void). There are multiple types of actions (e.g. SetStateAction).
        • As such, React.SetStateAction<T> is an "action type" that mirrors setState, and is to be used with React.Dispatch<T>.
          • Note: The type for useState is: UseState<S> = (action: S | ((prevState: S) => S)) => void;.
        • So in other words, React.Dispatch and SetStateAction is a way to describe setState purely in types.
          • So if we have the ff. code: const [num, setNum] = useState(0), the type of setNum setter will be: Dispatch<SetStateAction<number>>.
        • In usage: the checked, and setChecked tuple receives the current state "checked", and its corresponding setter. *
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment