Importing React and Chakra

When importing React and Chakra UI, please import the entire module. This maximises code readability and speed of development.

They should look like this at the head of your file:

import React from 'react'
import * as Chakra from '@chakra-ui/react'

This means when you want to use React features, like a hook, you'll write it like this:

React.useEffect(() => {
  
  console.log("Something cool")

}, [])

And your Chakra components will look like this:

return (
  <Chakra.Text
        fontSize={{base: "14px", xl: "20px"}}
        lineHeight={{base: "20px", xl: "24px"}}
        letterSpacing={{base: "0.11px", xl: "0.22px"}}
        {...props}
        />
)

Use the default export for Pages, not Components

When you use Gatsby, your file-system routed 'pages' and 'templates' must be named as well as being the default export. Gatsby needs your pages and templates to be the default export, as it otherwise doesn't know what to load. Likewise, you'll need it to be a named default export, as without it, React-Fast-Refresh won't know what component to hot-reload. This means your page and template starting point might look like:

import React from 'react'
import {PageProps} from 'gatsby'
import * as Chakra from '@chakra-ui/react'

const BlogTemplate = ({}: PageProps): React.ReactElement => {

  reutrn <Chakra.Box>My page content here</Chakra.Box>

}

export default BlogTemplate

Meanwhile, your components are not encumbered by the same rules, and therefore we prioritise minimising the lines of code written. This means we're not going to export as default, and will instead export inline. This will look like:

import React from 'react'
import * as Chakra from '@chakra-ui/react'


export const SomeComponentName = (props: Chakra.BoxProps): React.ReactElement => {
  return (
      <Chakra.Box {...props}/>
  )
}

Adding easy typings with Chakra-UI

90% of the components you build shouldn't be thought about as 'new' components, but as 'reskinned' Chakra components. Therefore, it doesn't make sense (and would be bad practice) to create our own Typings from scratch, as Chakra provides Typings out of the box for all of their components, which we can use as-is, or extend with an interface. These are usually called Chakra.ComponentName + props - i.e. Chakra.Text has Chakra.TextProps.

By following this model, we handoff the heavy lifting to Chakra UI, and we can use our own 're-skinned' components in the same way as normal Chakra UI components. The below example shows how to create a new component that re-skins Chakra.Text to a client specification. If we build a re-usable component like this, and there's a second one in the design with different colour, or an on hover etc, we can re-use this component, without having to add new declarations in our type defs because they'll already be handled by Chakra.

import React from 'react'
import * as Chakra from '@chakra-ui/react'

export const SomeTextComponentName = (props: Chakra.TextProps): React.ReactElement => {
  return (
      <Chakra.Text
        fontSize={{base: "14px", xl: "20px"}}
        lineHeight={{base: "20px", xl: "24px"}}
        letterSpacing={{base: "0.11px", xl: "0.22px"}}
        {...props}
        />
  )
}

With this format as well, because you have {...props} at the end of the component, if you want to override an existing prop on the component like fontSize, you can. If multiple props with same key are passed to a component (i.e. going left to right), React will use the last one passed in (i.e. the right most/bottom most one)

This way you get incredible flexible components without much work.

Extending Chakra's typings for more flexibility

Let's talk about what if there was a custom attribute to add to this - maybe we want to add data ingest to the props, or a boolean value, or something else that isn't just a CSS prop that Chakra can handle natively. For this, we'll use the interface/extends syntax from Typescript to extend the provided Chakra Typing. We'll then destructure our props on the function itself, so that we have access to our new custom prop, and also the existing Chakra CSS props.

interface SomeTextComponentNameProps extends Chakra.TextProps {
  isLight?: boolean
}

export const SomeTextComponentName = ({isLight, ...props}: SomeTextComponentNameProps): React.ReactElement => {
  return (
      <Chakra.Text
        fontSize={{base: "14px", xl: "20px"}}
        lineHeight={{base: "20px", xl: "24px"}}
        letterSpacing={{base: "0.11px", xl: "0.22px"}}
        color={isLight ? "#fff" : "#000"}
        {...props}
        />
  )
}

You can see here we're declaring a new prop of type boolean (optional), but we're also inheriting the existing declaration from Chakra.TextProps.

And to access that prop without manually writing out the rest of the possible props that could be passed in, were using object destructure syntax in the input props itself.