Each component should have its own folder nested inside the /components
folder. The name of this folder should match the name of the component itself. Inside this folder you should have the following files:
data.js
contains a copy of the JSS fields
object for this component. This will get imported in unit tests and Storybook stories. If the component doesn't get called from Sitecore and doesn't have JSS fields, then this file isn't needed.
index.tsx
main React component
stories.js
Storybook stories
styles.ts(x) (optional)
if you use Styled Components for the component, they will live here
test.tsx
Jest unit tests
types.ts
Typescript types and interfaces the component
\
\
Before working on the React component itself we first need to compose simpler props for it instead of parsing through the JSS fields object directly. The goal is to turn something like this:
into this:
The composition file is located at lib/composition/index.js
and contains an exported function for each of the React components. The JSS fields object (and sometimes placeholder object) from Sitecore is passed in and destructured. Using the ComponentTypes
global Typescript type both as a reference and consistent naming convention amongst all of the components, you will use these as keys to build your return object which will be passed in as the final props object to your component.
In the following example, the QuickLinks component will get a single items
prop returned to it, which will consist of an array of objects
An important thing to note and best practice is to use optional chaining when reaching into an object. The (?.)
operator allows you to reach as far down into a nested object without first having to validate that the previous step in the chain is valid and exists first.
[MDN - Optional Chaining](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining)
\
\
We should always use function components with hooks over class components. Function components are 'stateless' and simple as they don't track state or have lifecycle and render methods like class components do. If you need to track state in your component, import the useState
hook.
If your return content doesn't have a wrapping div you will need to create a fragment by using a short syntax element <>
rather than a React <Fragment>
. The only exception is if you need to use a key
when using a map
. In these instances you will need to use a <Fragment>
[React Short Syntax](https: //reactjs.org/docs/fragments.html#short-syntax)
With ES6 and arrow functions you can omit the curly braces and the return
keyword if your component is immediately returning something.
Component names should always be PascalCased and exported at the bottom of the file. If there is more than one component to export you need to declare which one is the default export:
\
\
Where possible always destructure props and other objects/arrays where used. This will help in typing and keeping things clear and not hidden behind a root object. If you don't need to destructure out all of the properties of an object, use the ...rest
keyword and pass that in instead like so:
If you're not transforming the props names from the parent to the child, then you can omit setting them directly on the child and instead just spread them in with the {...rest}
property.
\
\
Our components should be broken down into small, reusable functions instead of one gigantic component.
Each component should do only one thing and only be responsible for the logic for that one thing. If your function is growing in size, it's probably time to refactor it into smaller, more focused components. This will also help in making our components more reusable, consistent and TESTABLE.
React | Typescript | Tailwind | Forms | Unit Tests
Bad
Good
Good
Bad
Bad
Good
Good
Bad
Good
Bad
Good