arrow-left

All pages
gitbookPowered by GitBook
1 of 15

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Component Creation

Docs

🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverview

hashtag
Component Creation - Practical Overview


Table of Contents:

  1. Intro

  2. Technical Overview

  3. Practical Overview

  4. Definition of Done

  5. Sitecore


If you'd prefer to get right to the point and skip the overview, . Otherwise, read on for an in-depth explanation of how one could approach a workflow via Storybook.

hashtag
Workflow - A Practical Overview

As a developer, you may find it convenient to develop your React component in isolation within Storybook, rather than developing on the page. This can give you the opportunity to preview it and get a feel for the data you will need. Once the component looks and behaves in the intended manner, it should be relatively simple to incorporate it into the broader application.

Where before we have focused on a logical overview of how components work and are used within the context of our Sitecore JSS setup and our custom React application, this section will focus on a practical suggested workflow for developing your component.

hashtag
The Approach

Whether or not you elect to decide to develop your React component with Storybook, you will need two things to get started: an index.tsx and the data to render with it (if your component gets data from Sitecore).

It will be challenging to know in advance the shape of the data that comes to your React component from Sitecore, so you may find it helpful to set up your component in a very basic way that simply accepts props and renders or logs them out. Then, you can set up a composition function that does the same.

Once your component is rendering, you can capture the data from Sitecore that is logged or displayed onscreen to create your data file. Then you can begin working on your composition function to ensure you are sending only the data your component needs in the simplest, flattest form possible.

As you consider the data your component will need, it might be a good time to think about how your component is supposed to behave and consider writing out some assertions to test against in your test.ts file.

After that, you can work in Storybook by importing your component's composition function into your story.js file and process your dummy data with it. At this point, you will have dummy data that looks just like what is coming from Sitecore and you can develop your component in Storybook, just as you would a regular website.

An example of that might look something like this:

Finally, once your component is close to completion, you can finish writing your unit tests to ensure your component functions as expected.

The following section summarizes things into a concise checklist.

hashtag
Checklist

< Previous Next >

Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

directory.
  • there's a handy checklist herearrow-up-right
    // MyComponent/composition.tsx
    
    const MyComponent = (sitecoreData) => {
      return sitecoreData;
    };
    // MyComponent/index.tsx
    
    /** technically, you would want to provide types in a Typescript file,
     * but we're just trying to get some data here.
     */
    
    const MyComponent = (scData) => {
      console.log(scData);
      return <div>{JSON.stringify(scData)}</div>;
    };
    
    // Now you can copy the payload from your browser and paste it into your brand new data file!
    // story.js
    import React from "react";
    import MyComponent from "./index";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    import Data from "./data";
    
    /**
     * create a variable with data that is in the shape that your component will be
     * expecting, as it would be from your composition file.
     * Again, this is necessary since the composition function is not called by Storybook.
     */
    
    const props = MyComponentComposition(Data);
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      /* here you can spread those props into your Storybook file, making data
       * available to your component as it would be on the actual live page.
       */
      ...props,
    };

    Analytics

    Adding analytics to a component is pretty straightforward,

    First set up your analytics object in your composition method:

    const { compositionFunction, component } = Composition(NavCard)(({ fields, params }) => {
    
    const items = fields?.items.reduce(
        (acc: Parameters<typeof NavCard>[0]['items'], curr: typeof fields) => {
          return [
            {
              ...
              analytics: {
                category: 'nav-card_rectangular',
                label: title?.value || '',
                action: [cta?.text?.value, cta?.href].filter(Boolean).join(' | '),
                guid: link?.value?.id || '',
                event: 'event-click',
              },
            },
          ];
        },
        []
      );

    Then import the track object from src/lib/Analytics into your component, and grab the analytics data from the composition props. After that, simply attach a handler to the correct component node, to call the track.component method and send the data to analytics:

    hashtag
    Testing Analytics

    When not in production, analytics data will be sent to the console instead of to Google. To verify that your analytics are being setup and used correctly, simply open the console, trigger the analytics method, and you should see something similar to the following:

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    import track from 'src/lib/Analytics';
    ...
    
    const ComponentName = ({
      ...
      analytics,
    }: PropsType) => {
    
    ...
      <Button
        ...
        onClick={() => track.component(analytics)}
      >
        Click Me!
      </Button>

    DefinitionOfDone

    hashtag
    Component Creation - Definition of Done


    Table of Contents:

    1. Intro

    2. Technical Overview

    3. Practical Overview

    4. Definition of Done

    5. Sitecore


    hashtag
    Our "Definition of Done" for a Component

    You will know your component is complete if it is properly formatted, you have the requisite files outlined in the previous section (where applicable), and your files are free of TypeScript and ESLint warnings. Additionally, the appearance of the component should match the look of the Abstract design if one is provided, and if any Acceptance Criteria is provided, that should be met as well. We prioritize accessibility as well, so your component should pass [WCAG2.1 a11y requirements](https: //www.w3.org/WAI/standards-guidelines/wcag/glance/). This can be validated via the Storybook plugin we have set up, as well as in the browser using Lighthouse.

    Lastly, you will need to have written some unit tests to check the core functionality of your component and ensure that those are passing.

    hashtag
    Checklist


    In summary, your component should do the following...


    If you have met all of these requirements, then you, intrepid developer, have a component worthy of submission to PR!

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Setup

    This section outlines getting the application (for the JSS public site) setup for development.

    1. Get the repo

      1. cd to where you keep your repos

      2. clone the project from https: //bitbucketp.duke-energy.com/projects/DUKCOM/repos/dxt-jss-public/browse 1. In your terminal (command line), run git clone https: //bitbucketp.duke-energy.com/scm/dukcom/dxt-jss-public.git

    2. Install dependencies

      1. Use node -v 14.17.4

      2. Set npm registry to Nexus (prod)* 1. run npm config set registry https: //nexus.duke-energy.com/repository/duke-cne-npm/ on the command line

    3. Setup .jssconfig and .env

      1. Add scjssconfig.json file (or rename scjssconfig-sample.json → scjssconfig.json)

        //scjsstest.duke-energy.com" } }

    4. Add .env file (or rename .env-sample → .env)

      1. Add:

        //` for development so that jurisdiction cookies will work properly.

    5. Setup your .hosts file

      1. We need to set the local IP to local.duke-energy.com so we can use cookies set by the .duke-energy.com domain.

    6. Start the project for development.

      1. Inside the project, run npm run start:connected on the command line.

      2. After the app successfully starts up, change the URL to https: //local.duke-energy.com:3000/home.

    7. Do some cool stuff

    *Why are we using Nexus rather than npm for package installs?

    @de-electron is a scoped npm package for [Electron Design System](https: //electron.duke-energy.com) dependencies which lives in Nexus, Duke Energy's private internal package repository. You can use Nexus to install all necessary npm packages for the project.

    TechnicalOverview

    hashtag
    Component Creation - Technical Overview


    Table of Contents:

    1. Intro

    2. Technical Overview

    3. Practical Overview

    4. Definition of Done

    5. Sitecore


    hashtag
    A Technical Overview


    hashtag
    Generate Component Factory

    The Generate Component Factory (scripts/generate-component-factory.js) generates the /src/temp/componentFactory.js file which maps React components to JSS components.

    The component factory is a mapping between a string name and a React component instance. When the Sitecore Layout service returns a layout definition, it returns named components. This mapping is used to construct the component hierarchy for the layout.

    The default convention uses the parent folder name as the component name, but it is customizable in generateComponentFactory().

    Sometimes Sitecore references a component in a way that is different from what we want the component to be called within the application. In this case, we can provide an alias to map the Sitecore name to our component upon import:

    In this case, we are telling our app that if it receives a string called HeroCarousel or HeroCarouselNocache, use the 'Hero' component. Likewise, if a string of JssAccordion is encountered, use the 'Accordion' component, and so on.

    If the name of your component and its Sitecore counterpart are the same, there is no need to include an alias.

    hashtag
    Passing Down Data Via Composition

    At its simplest, the goal of the composition file is to transform the data being passed to your component from accessing deeply nested properties like this:

    into this:

    What does that entail exactly?

    The composition file also exists in the your component directory. Sitecore sends a wealth of information down to the page related to each component. Not all of this information is needed or even useful. So the purpose of each of these functions is to strip away the unneeded data, giving our component only what it needs to render properly.

    To illustrate this, in the following example, the QuickLinks component will get a single items prop returned to it, which will consist of an array of objects:

    As a developer, you will be responsible for writing the composition function for your component. Since the data from Sitecore comes in all sizes and shapes, there is no "one-size-fits-all" solution, but most likely you can take a cue from the composition functions that have been written before to get a feel for how to approach a new one.

    hashtag
    Adding files for your component

    Now that you know how components are implemented within our app, how is a component actually created within the app?

    Generally, each component you create will consist of at least one of the following items:

    • an - your React file.

    • a file for transforming incoming data into more concise props

    • a - a file for your unit tests.

    Read on for more detail.

    hashtag
    Index.tsx

    The index file will be your main component React file, using Typescript. At a minimum, your component will require this file. Since our project uses Tailwind, your styles will mainly exist here as well. for more info.

    hashtag
    Test.tsx

    The test file is where your unit tests live. Tests are written in React with Typescript, using [Jest](https: //jestjs.io/en/) and [Enzyme](https: //enzymejs.github.io/enzyme/).

    Jest is a popular testing library and Enzyme adds some sugar on top to make writing the tests a little easier.

    You should be testing the basic functionality of your component. This may be as simple as validating that your component is rendering to mocking data and checking to make sure your component is outputting it correctly.

    In all likelihood, you will need a test for every component you write, even if it is a very small test to confirm that the component is properly mounted.

    hashtag
    Types.ts

    This file is for type definitions and interfaces for [Typescript](https: //www.typescriptlang.org). You can store your component-specific definitions in this file and export them to your index file.

    For more information on writing Typescript within our app, please check out our Typescript Style Guide.

    hashtag
    Stories.js

    The stories.js file creates a component. To be honest, the paradigm of writing in Storybook can be a little bit tricky to grasp at first if you don't have experience with it. And to be frank, their docs could use some work. So you are encouraged to .

    In essence, however, consider your Storybook file something like an environment for your React component that enables you to run it in isolation from the rest of the app. This makes it easy to preview the functionality and, as a result, developers and designers can easily access the React component without having to navigate to a route to interact with it. This also enables you to view and manipulate it in ways that would be difficult in the context of an entire app.

    If your component is extremely simple and self-contained, a Storybook file may not be necessary, or perhaps even possible. But in general, all components should have a Storybook file.

    Unlike most of our other component files, we are not using Typescript in Storybook, so it is not necessary to provide types within your story file.

    hashtag
    Data.js

    The data.js file is meant to provide mock data to your unit tests and Storybook files. It is ignored by Webpack, so don't include any objects or functions your component needs in order to render properly.

    The data file does not use Typescript since it is generally only used to provide mock JSON data.

    Perhaps the most frequent use case for this file is that you can copy/paste the data exactly as it comes from Sitecore. Since Storybook files and unit tests exist in isolation, they won't receive Sitecore data. But your React component will be relying on data to render correctly. So the dummy data placed within the data file ensures your React component will still have similar data in the same shape as the Sitecore data it would have in real-world conditions. Thus, even in isolation, you can ensure your component looks and behaves in a consistent and predictable manner.

    On occasion, you may encounter a situation where you have a hard-coded object or map that is used for reference within your app. While these can be useful, sometimes they may clutter up your main index.tsx file. In this case, you may be tempted to house those values in the data.js file and import them into your React component from there. But as was mentioned above, these files are ignored by Webpack, so any necessary additional data you would want to exist in a file should be added to a file named something other than data.js.

    Depending on the complexity of your component, this file may not be necessary. For example, if your React component doesn't get called from Sitecore, doesn't have JSS fields, or it doesn't require any extra hard-coded data, then you will not need a data file.

    hashtag
    The folder

    Of course, all of these files must live somewhere, and that place is in a folder. This folder should of course be named after your component and added to the /components directory. Resist the temptaion to take creative liberties with the name of the folder, as this will be the folder that will be imported into the Component Factory (see /src/temp/componentFactory.js) and importing a folder named TheGreatestComponentYouHaveEverFeastedYourMiserableEyeballsOn for the Modal component won't make a lot of sense out of context, no matter how fitting it seemed in the moment.

    < Previous Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Forms

    Dynamically generating forms with React and Sitecore JSS

    hashtag
    Creating the FormField

    1. Navigate to page http: //local.duke-energy.com:3000/home/products/outdoor-lighting/contact

      • This page calls the <SingleStepForm /> component in the layout json from Sitecore which have fields that look like this

    2. <SingleStepForm /> gets rendered via its composition file converts this JSS fields object as its props.

    3. <SingleStepForm /> checks to make sure that modelJson.value exists before parsing it from a string to actual json, creating the variable formModel.

      • fields.ModelJson ie: formModel contains data for every field and their props. It's an array of objects where each object contains information about that field. After parsing the JSS, formModel

    4. formModel then gets passed into createFormInit() to process all of this data into something more concise and manageable

    const createdFields = useMemo(() => createFormInit(formModel, false), []);

    • The formModel array will get passed into parseFields()

    • parseFields() will then run each of the form objects that are inside the formModel through yet another and final parser, createForm(). It will then save these objects to a new array and filter out any nulls generated by createForm()

    \

    hashtag
    Side note: Important info

    1. Since there is a lot of very important information missing from the fields.ModelJson, (input type, hidden fields, etc) from Sitecore we need a way to dynamically figure all of this out the best we can, with what we've been given. This is where mapping.ts comes in. This file contains 3 sections:

      • inputMap

    const inputMap: CFMappingType["inputMap"] = { alternatephone: "phone", "alternate_phone_#": "phone", captcha: "recaptcha", checkbox: "checkbox", checkbox_list: "checkboxGroup",

    // ... };

    • regexMap

      • regexMap is an object with either a validationPattern name or input type as the key with the value being an object containing an error message and RegEx pattern to be used in validation

    const regexMap: CFMappingType["regexMap"] = { email: { message: "Not a valid email format", value: /^[^\s@]+@[^\s@]+.[^\s@]{2,}$/, }, lettersWhiteSpace: { message: "Can only be letters and spaces", value: /^[a-zA-ZáéíóúüÁÉÍÓÚÜñÑ-]+(\s+[a-zA-ZáéíóúüÁÉÍÓÚÜñÑ]+)*$/, }, notSameDigits: { message: "Can only contain numbers", value: /^(\d)\d*(?!\1)\d+$/, },

    // ... };

    const { file, props } = dataMap[inputType];

    const Component = loadable(() => import(src/components/Form/${file}));

    • Component: The React component that was dynamically imported

    • data: An object containing details about the field such as maxLength, minLength, placeholder, required, etc..

    • file: The name of the component that was dynamically imported

    • id: A unique id for each field since sitecore doesn't return usable id's

    • props: The props returned from the dataMap lookup along with the column width for each field

    1. <SingleStepForm /> then iterates through the createdFields array and will:

      • pass the props.columns value to <FieldWrapper /> to determine how wide the field should be in CSS

    hashtag
    Inside the FormField

    1. Inside the /components/Form folder are all of the FormField components. These components are the default exports from each of these files such as <FormInput />

      • The purpose of these components is to take the createForm() generated props that were passed in via the <SingleStepForm /> and to 'normalize' them into simpler generic props for the <Input />

    const FormInput = ({ data, errors, name, props, register, validations, }: FormInputProps) => {

    const { mask, type } = props || {};

    const { label = "", maxLength, required, toolTipText } = data;

    const propData = { error: { hasError: Boolean(errors[name]?.message), errorMessage: errors[name]?.message, }, id: label, label, mask, maxLength, name, register, required, type, validations, };

    hashtag
    MultiStepForm

    The <MultiStepForm /> is nearly identical to <SingleStepForm /> except that it has multiple steps to the form. Like <SingleStepForm />, the fields data will also get pushed to createFormInit() and from there to multiStepFormFields(). It's here that the fields will be parsed through parsedFields() and then afterwards they will be separated into an array that contains the <FormStepper /> fields first and then the fields for the first screen, second screen and so on. This array will look something like this:

    When this array is finally returned back to <MultiStepForm>, the form stepper data is parsed out and passed into the <FormStepper /> component and the first array of field objects in the remaining array will be rendered out.

    We need to map through the outer array first and then loop through each of the inner array's to render each of the fields. The fields that are not shown on the current step will get a class added to FieldWrapper as hidden, all of the other fields will get the class, block. Since react-hook-form tracks our inputs via a ref, its much easier to just build out all of the steps of the form at once and just hide/show the active fields via CSS. This way we don't lose the ref when the fields get mounted/unmounted from the DOM.

    We also need to account for required fields that are now 'hidden' due to not being contained in the activeStep. This will cause an error in the browser saying that a required field is not focusable. We get around that by toggling the 'required' prop for that field depending on whether or not its currently in the activeStep

    React | Typescript | Tailwind | Forms | Unit Tests

    Sitecore

    hashtag
    Component Creation - Sitecore


    Table of Contents:

    1. Intro

    2. Technical Overview

    3. Practical Overview

    4. Definition of Done

    5. Sitecore


    hashtag
    Sitecore and You

    In the context of our application, we treat Sitecore like a CMS for our React front end. We use it to tell our application where components should be and provide data and assets.

    But Sitecore is not for the faint of heart. Many devs like to get their hands dirty by just "getting in there" and poking around. However, due to its terrible UI and grinding sluggishness, working within Sitecore can be maddening. To top it all off, as is so often the case, the documentation for Sitecore is substandard. God willing, you won't need to venture into Sitecore very often. But if you have exhausted all other options and you still find you need to work within it, it's better to go in with a clear gameplan of what you want to accomplish and how to accomplish it. This section aims to help you with that.

    hashtag
    Sitecore Component Creation Overview

    Once upon a time, new components were built by the front end team and then handed off to the Sitecore developers for integration into the application. These days, components built by the front end team stay within the front end application, making the workflow much more efficient. New components can then be integrated into the CMS for use by the content team, with editable fields intact.

    While many components are already available for our application in Sitecore, if you find that you need to add a component to be edited within Sitecore, this document contains an overview to the process you will need to follow.

    Before delving into that, it is helpful to understand at a high level how Sitecore is being used in our implementation.

    A "page" such as /Home or /Billing consists of a collection of Renderings within Sitecore. A rendering could be viewed as a representation of a React component within Sitecore. These renderings are associated with data relevant to the component and placeholder info. A Placeholder is a way for content authors and marketing folks to specify where upon a page a component should be injected. In order for a rendering to be displayed upon a page, it will need to be assigned a placeholder.

    When a page exists within Sitecore, it is made available to our application via something called the Layout Service, which exposes an endpoint that can be requested. This endpoint returns a serialized JSON payload of the rendering info from Sitecore. While a traditional Sitecore implementation would involve C# and Razor templates, this implementation makes it possible for us to use React on the front end.

    hashtag
    Basic Instructions for Creating a Component

    1. Navigate to [Sitecore](http: //scdev25.duke-energy.com/sitecore) and login. From there, expand "sitecore > Layout > Renderings > Custom > Common" and you can see the available renderings. If you need a rendering to map to your component that isn't already available here, you can create one.

    2. Create a Rendering. Within the appropriate folder under /sitecore/layout/renderings, create a new Json Rendering. This will most likely be within the Custom/Common folder, and can be done either by right-clicking the folder and selecting Insert > Json Rendering or selecting the folder you want to create a rendering in and then pressing the Json Rendering button from the options.

    < Previous

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    Tailwind CSS

    Tailwind CSS is a utility-based styling library. In order to streamline and standardize things like colors and spacing within our application, an Electron theme has been created to extend Tailwind's functionality, thus making it easy for us to access some standard Duke colors, fonts, etc. As a result, you will get most of the magic of Tailwind, but with most of the colors, text, and sizing options overwritten to reflect Duke's design system.

    We won't talk too much about the decisions behind why we are using this implementation here, but Chris Greufe has already done an incredible job documenting the ins-and-outs of it [here](https: //electron.duke-energy.com/foundation/utilities/utility-first). If you want to know more of the granular aspects and philosophy to our approach, you are encouraged to give it a read.

    Instead, this doc will mostly focus on how the dev team actually uses this implementation and a bit about our approach, as well as a proposed style guide.

    hashtag
    Philosophy and Approach

    Tailwind CSS is our primary way of styling within the DXT-JSS-Public React App. A lot of effort and resources have been put into making Tailwind and Electron do as much of the heavy lifting for us as possible, so for maintainability's sake, every effort should be made to find a solution to style your work with Tailwind. If you're hitting a wall trying to figure out an approach that works within Tailwind, don't hesitate to reach out to a teammate. If you find yourself in the rare situation where you encounter something that simply cannot be resolved using Tailwind, we use [Styled Components](https: //styled-components.com) as our fallback. If you find that you are creating a styled component often to deal with an edge case, it's probably worth documenting [here](https: //confluence.duke-energy.com/display/DEPW/Tailwind+requests).

    hashtag
    Tooling

    There's not a lot of tooling required for Tailwind but [the Tailwind CSS Intellisense VSCode plugin](https: //marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss) can be pretty helpful. Once installed, it gives suggestions for Tailwind classes as well as Electron-specific ones. It also shows a preview of the CSS rules that the utility class utilizes.

    hashtag
    Enabling Tailwind Properties

    Although Tailwind provides the flexibility to apply a wide range of modifiers (called variants in Tailwind) to a variety of CSS rules, you may occasionally find that a Tailwind class is not behaving the way you expect in a responsive context, or upon hover. This may mean that you will need to edit the Tailwind config. [You can find more info about that here.](https: //tailwindcss.com/docs/configuring-variants) Please be careful to [extend the values](https: //v1.tailwindcss.com/docs/configuring-variants), rather than simply adding them to the variant object, which will overwrite all the defaults.

    hashtag
    Key Differences

    The main difference you'll find between Tailwind's approach and Electron's is that Duke doesn't need an extensive color library. As a result, you'll find that something like text-blue-800 or bg-yellow-200 does not behave as you'd expect. Most likely you will be looking for something like text-blue-dark or bg-yellow. So the color palette will be limited, and rather than a number to modify the color, there will either be no modifier, or a modifier of -light, -lighter, -dark, or -darker.

    hashtag
    Style Guide

    Because almost all of our styles exist within utility classes, there is often no need for traditional CSS classes to style a block. It's fairly unusual to need to add a class like wrapper or large BEM classes. Occasionally, you may need to add a class to make it easier for unit tests to search for a selector. In such a case, we suggest that you use a js- prefix, and that you place it at the beginning of your utility classes.

    example:

    Often, the amount of classes you need to style a complex element can be rather long. In this case, it is suggested that you group your classes conceptually. Since Tailwind is a mobile-first framework, it makes sense to start with "base" styles that will be present across all sizes of the component, immediately followed by the responsive counterparts, in ascending order (md, lg, xl, etc).

    Of the base styles, start with sizing (height, width, padding, margin) and other fiddly rules so that they are easily accessible to you and anyone who may need to maintain your code in the future. Utility classes that represent rules that are easily identifiable at a glance, such as text color or background color, should come secondary.

    That was a lot of words, so let's look at an example.

    🚫 Bad

    ✅ Good

    That may seem a lot to unpack, so let's examine that for a second. Note that the classes start with the width property and then are immediately followed by the responsive variant. This pattern follows as we move through the margin and into the padding, which is then followed by the display types, and onto more obvious styles like font and background colors, which don't require a check of the code in order to verify the values. Finally, the rules end with the transition and duration properties, which are often "set and forget" rules.

    hashtag
    Resources

    • [Styled Components](https: //styled-components.com) - documentation in case you need to step outside the Tailwind garden

    • [Tailwind Requests](https: //confluence.duke-energy.com/display/DEPW/Tailwind+requests) - add to the Tailwind/Electron wishlist!

    • [Electron Docs](https: //electron.duke-energy.com/foundation/utilities/utility-first)

    React | Typescript | Tailwind | Forms | Unit Tests

    React

    hashtag
    Folder and File Structure

    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

    \


    \

    hashtag
    Composition File

    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

    hashtag
    Optional Chaining

    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)

    \


    \

    hashtag
    Function Components

    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.

    hashtag
    Use Short Syntax Over React Fragments

    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)

    🚫 Bad

    ✅ Good

    ✅ Good

    hashtag
    Use Implicit Returns

    With ES6 and arrow functions you can omit the curly braces and the return keyword if your component is immediately returning something.

    🚫 Bad

    🚫 Bad

    ✅ Good

    ✅ Good

    hashtag
    Use PascalCase and Export At The End Of The File

    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:

    \


    \

    hashtag
    Spread and Destructure

    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:

    🚫 Bad

    ✅ Good

    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.

    \


    \

    hashtag
    Small, Focused and DRY

    Our components should be broken down into small, reusable functions instead of one gigantic component.

    🚫 Bad

    ✅ Good

    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

    Storybook

    Storybook is an open source tool for developing UI components in isolation. It essentially gives us a sandbox to showcase and test the components we'll use throughout our app.

    It allows us to develop more efficiently (we can develop our components in isolation within Storybook, rather than developing on the page of the app), and also allows the design and UI teams to interact with our components as we build them. They can also change the values of the component's props to see how the component will react as it receives different data.

    To get it up and running navigate to the app repo and, from the command line, enter:

    Let's first discuss some of the core principles of Storybook (we'll use examples from our own app where possible), before then diving into the anatomy of a typical story.

    address any provided Acceptance Criteria
  • You will be prompted by a modal to choose a name for your item. In the input labelled "Enter a name for the new item", provide the name of the React component that will be used for the rendering.

  • You can now add your newly-created Json Rendering to a page. If appropriate, you will also need to specify a datasource item for the rendering. The datasource item will need to be created from a data template in Sitecore.

  • When Layout Service is rendering, the fields from the datasource item will be serialized and added to the Layout Service output and should now be available to our application.

  • README

    Run npm install on the command line

    On the command line, run sudo vim /etc/hosts
  • Add a new line and enter 127.0.0.1 local.duke-energy.com

  • Press Esc key and then :x to save and exit

  • a types.ts filearrow-up-right - for Typescript definitions and interfaces

  • a stories.js filearrow-up-right - for Storybook..

  • a data.js filearrow-up-right - for mock Sitecore data.

  • a 📁 containing each of these filesarrow-up-right - It's a folder.

  • index.tsx filearrow-up-right
    composition.tsxarrow-up-right
    test.tsx filearrow-up-right
    See the styles sectionarrow-up-right
    Storybookarrow-up-right
    check out our documentation for Storybook.arrow-up-right

    [Tailwind VSCode Intellisense Plugin](https: //marketplace.visualstudio.com/items?itemName=bradlc.vscode-tailwindcss)

  • [Nerdcave CheatSheet](https: //nerdcave.com/tailwind-cheat-sheet) - some random guy made a really handy cheatsheet for Tailwind CSS. Obviously, our rules won't be on it, but it's a nice quick reference for a lot of the classes.

  • [Tailwind CSS Docs](https: //tailwindcss.com/docs) - their official docs are better than most

  • [Configuring Variants](https: //v1.tailwindcss.com/docs/configuring-variants) - case in point

  • hashtag
    Stories

    A story, much like a React component, is a function that describes how to render a component.

    You can have multiple stories per component, meaning Storybook gives us the power to describe multiple rendered states.

    For a very simple example using the PushDownPanel component from our own app, we might first describe the component with it's default state, but then add a story that describes a different rendered state.

    So here's how the initial default PushDownPanel might look:

    And then we can add variations based on different states, for example a version that uses icons:

    And the new stories will show up in the sidebar navigation, like so:

    hashtag
    Args

    Story definitions can be further improved to take advantage of Storybook's "args" concept.

    A story is a component with a set of arguments (props), and these arguments can be altered and composed dynamically. This gives Storybook its power (they even refer to this as a superpower in their docs!), allowing us essentially to live edit our components.

    Most of the time the type of arg will be [inferred automatically](https: //storybook.js.org/docs/react/api/argtypes#automatic-argtype-inference), however we can use ArgTypes to further configure the behavior of our args, constraining the values they can take and generating relevant UI controls.

    hashtag
    Controls

    Controls allow designers and developers to explore component behavior by mucking about with its arguments.

    Storybook feeds the given args property into the story during render. Each of the args from the story function will now be live editable using Storybook's Controls panel, so we can dynamically change components in Storybook to see how they look with different settings and data:

    This essentially evolves Storybook into an interactive documentation tool, allowing developers and stakeholders to stress test components, for example by adding huge strings of text that might help expose UI problems.

    Controls can be configured to use UI elements that we deem most appropriate according to the data that we're trying to manipulate; some examples from our own components include a radio button for manipulating background color:

    A select menu to control the number of items that render:

    And for our Hero component, a mixture of text inputs, boolean switch and radio group (screen shot of how these controls render follows the code):

    hashtag
    A11y and Other Addons

    Addons are plugins that extend Storybook's core functionality, packaged as NPM modules. Once [installed and registered](https: //storybook.js.org/docs/react/addons/install-addons) they will appear in the addons panel, a reserved place in the Storybook UI below the main component.

    One such addon we use is the Accessibility addon, which helps to make our UI components more accessible. Simply select the Accessibility tab from the aforementioned addons panel, and there you will see any Violations, Passes and Incomplete requirements pertaining to accessibility.

    We also use the Viewport [toolbar](https: //storybook.js.org/docs/react/get-started/browse-stories#toolbar) item, which allows us to adjust the dimensions of the iframe our stories are rendered in, making it nice to test responsive UIs.

    hashtag
    Anatomy of a Story

    Stories exist alongside the other component files as stories.js.

    Here's an example of how a typical story might take shape:

    Let's break this down line by line:

    Component Setup: So we start off with your normal component setup, standard stuff: importing React, importing your component and (if your component gets data from Sitecore) importing any data that might be required.

    Data Composition: After the component setup, we next move on to the process of essentially distilling the data recieved from Sitecore into only the parts we need - you can read more about this process in not only one spot of our docs, but two! Here and here.

    Exporting Stories: After composing the data, we move on to one of the key ingredients of a story: the default export that describes the component. It's a function that returns a component's state given a set of arguments; it describes how to render a component.

    It's a story! Behold. Don't get them wet, don't expose them to bright light, and most importantly don't feed them after midnight!

    Were we to add any other stories beyond the default, we would then add named exports that would describe the additional stories.

    We can also add ArgTypes here if we need to configure our args beyond Storybook's automatically-generated UI controls.

    In the example above, we're setting backgroundColor as a radio button so the user can choose between different background colors, and setting the default as white.

    Template Definition: Now that our stories are exported, we move on to defining a master template (Template) for our component's stories, and passing in our args.

    We can then reuse this template across stories. Template.bind({}) makes a copy of the function, reducing code duplication. This is a [standard JavaScript technique](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind) for making a copy of a function, and allows each exported story to set its own properties.

    And finally we spread in our component's props (...props), making data available to our component as it would be in the regular app.

    hashtag
    storybookCustomArgs

    The way in which Storybook automatically infers a set of argTypes based on each component's props can often lead to a lot of unnecessary UI controls rendering.

    We have a function for that! In the lib/helpers/index.ts file you can find a very cool function called storybookCustomArgs, which allows us to configure which props we would like to render controls for in the Storybook Controls panel.

    hashtag
    createStoryOptions

    Oftentimes our component data will come through to us a single array, but in order for Storybook to render the controls in our desired way, we need that data to be a multi-dimenstional array.

    Again we have a nifty function for that! Here's an example of both storybookCustomArgs and createStoryOptions Storybook helper functions at work:

    hashtag
    Resources

    • [Learn Storybook](https: //www.learnstorybook.com) - a guided tutorial through building a simple application with Storybook

    • [Component Driven User Interfaces](https: //www.componentdriven.org) - learn more about the component-driven approach that Storybook enables

    • [Storybook Addons](https: //storybook.js.org/addons) - supercharge Storybook with advanced features and new workflows

    • [Component Story Format](https: //storybook.js.org/blog/component-story-format/) - read more about the Component Story Format (CSF), a new way to author stories based on ES6 modules

      1. Add:
    
         ```
         {
           "sitecore": {
             "instancePath": "",
             "apiKey": "{9F777224-3275-4D56-BD29-371FB3C00821}",
             "deploySecret": "",
             "deployUrl": "",
             "layoutServiceHost": "https:
      ```
      HTTPS=true
      ```
    
      This will allow you to use `https:
    const componentAlias = {
      HeroCarousel: "Hero",
      HeroCarouselNocache: "Hero",
      JssAccordion: "Accordion",
    
      // ...
    };
    <Component title={fields.PodItemLink?.value?.text} />
    <Component title={title} />
    // composition/index.js
    
    const QuickLinks = ({ fields }) => {
      const items = fields?.QuickLinkItems?.reduce(
        (acc, curr) => [
          ...acc,
          {
            image: { ...curr.Icon?.value },
            link: curr.Link?.value?.href,
            title: curr.Title?.value,
            id: curr.Id,
          },
        ],
        []
      );
      return { items };
    };
    // components/QuickLinks/index.tsx
    
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    <div className="js-form text-blue-dark..." />
    <div className="text-blue transition-all md:px-32 mt-12 flex bg-black w-20 duration-500 md:block px-24 lg:w-16 lg:px-48" />
    <div className="w-20 lg:w-16 mt-12 px-24 md:px-32 lg:px-48 flex md:block text-blue bg-black transition-all duration-500 />
    <Component title={fields.PodItemLink?.value?.text} />
    <Component title={title} />
    // composition/index.js
    
    const QuickLinks = ({ fields }) => {
      const items = fields?.QuickLinkItems?.reduce(
        (acc, curr) => [
          ...acc,
          {
            image: { ...curr.Icon?.value },
            link: curr.Link?.value?.href,
            title: curr.Title?.value,
            id: curr.Id,
          },
        ],
        []
      );
      return { items };
    };
    // components/QuickLinks/index.tsx
    
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    import React, { useState } from "react";
    
    const Button = () => {
      const [count, setCount] = useState(0);
      return <button onClick={setCount(count + 1)}>clicked {count} times!</button>;
    };
    const ChildComponent = () => {
      return (
        <Fragment>
          <div>Title</div>
          <div>SubTitle</div>
        </Fragment>
      );
    };
    const ChildComponent = () => {
      return (
        <>
          <div>Title</div>
          <div>SubTitle</div>
        </>
      );
    };
    const Component = ({ items }: { items: Array<ComponentTypes> }) =>
      items?.map(({ id, ...rest }, index) => (
        <Fragment key={id}>
          <div>The title</div>
          <InnerComponent index={id} length={items.length} {...rest} />
        </Fragment>
      ));
    const ChildComponent = () => {
      return <div>The Child</div>;
    };
    const ChildComponent = () => {
      return (
        <div>
          <Component />
          <Component />
          <Component />
        </div>
      );
    };
    const ChildComponent = () => <div>The Child</div>;
    const ChildComponent = () => (
      <div>
        <Component />
        <Component />
        <Component />
      </div>
    );
    const ChildComponent = () => <div>The child</div>;
    
    const ParentComponent = () => (
      <>
        <h1>The Title</h1>
        <ChildComponent />
      </>
    );
    
    export { ChildComponent };
    export default ParentComponent;
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ image, link, title, id }, index) => (
          <QuickLinksItem
            image={image}
            index={index}
            length={items.length}
            link={link}
            title={title}
            key={id}
          />
        ))}
      </QuickLinksWrapper>
    );
    const QuickLinks = ({ items }: { items: Array<ComponentTypes> }) => (
      <QuickLinksWrapper>
        {items?.map(({ id, ...rest }, index) => (
          <QuickLinksItem index={index} length={items.length} key={id} {...rest} />
        ))}
      </QuickLinksWrapper>
    );
    const DataReport = ({ items }: { items: Array<string> }) => (
      <table>
        <tr className="bg-teal-dark text-lg text-white">
          <td className="cell-class">NAME</td>
          <td className="cell-class">HOURS</td>
        </tr>
        {items.map(({ hours, name }, index) => (
          <tr className="bg-transparent text-lg text-teal-dark" key={index}>
            <td className="cell-class">{name}</td>
            <td className="cell-class">{hours}</td>
          </tr>
        ))}
      </table>
    );
    const TableCell = ({ children }) => (
      <td className="cell-class">{children}</td>
    );
    
    
    const TableRow = ({ children, isHeader }) => {
    
    const class = isHeader ? 'bg-teal-dark text-white' : 'bg-transparent text-teal-dark'
      return <tr className={`${class} text-lg`}>{children}</td>
    };
    
    
    const DataReport = ({ items }: { items: Array<string> }) => (
      <table>
        <TableRow isHeader={true}>
          <TableCell>NAME</TableCell>
          <TableCell>HOURS</TableCell>
        </TableRow>
        {items.map(({ hours, name }, index) => (
          <TableRow key={index}>
            <TableCell>{name}</TableCell>
            <TableCell>{hours}</TableCell>
          </TableRow>
        ))}
      </table>
    );
    npm run storybook
    export const NoIcons = Template.bind();
    NoIcons.args = {
      items: itemsOptions["3"],
    };
    export const Icons = Template.bind({});
    Icons.args = {
      items: itemsOptions["5"],
    };
    export default {
      title: "Components/Accordion",
      component: AccordionComponent,
      argTypes: {
        theme: {
          name: "Theme",
        },
        closeOthers: {
          name: "One Panel Open At A Time",
        },
      },
    };
    
    const Template = (args) => <AccordionComponent {...args} />;
    
    export const Primary = Template.bind({});
    
    Primary.args = {
      ...props,
    };
    argTypes: {
      backgroundColor: {
        control: {
          type: 'radio',
          options: ['white', 'gray'],
        },
        defaultValue: 'white',
      },
    },
    argTypes: {
      items: {
        name: 'How many items?',
        control: {
          type: 'select',
          options: {
            ...itemsPrimary,
          },
        },
      },
    },
    argTypes: {
      title: {
        control: {
          type: 'text',
        },
      },
      subtitle: {
        control: {
          type: 'text',
        },
      },
      link: {
        control: 'boolean',
      },
      ctaType: {
        control: {
          type: 'inline-radio',
          options: ['ImageSlide', 'VideoSlide'],
        },
      },
    },
    import React from "react";
    import MyComponent from "./index";
    import { Data } from "./data";
    import { MyComponent as MyComponentComposition } from "../../lib/composition";
    
    const props = MyComponentComposition({ fields: Data });
    
    export default {
      title: "Components/MyComponent",
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: "radio",
            options: ["white", "gray"],
          },
          defaultValue: "white",
        },
      },
    };
    
    const Template = (args) => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    import React from 'react';
    import MyComponent from './index';
    import { Data } from './data';
    
    ...
    ...
    
    import { MyComponent as MyComponentComposition } from '../../lib/composition';
    
    
    const props = MyComponentComposition({ fields: Data });
    
    ...
    ...
    
    export default {
      title: 'Components/MyComponent',
      component: MyComponent,
      argTypes: {
        backgroundColor: {
          control: {
            type: 'radio',
            options: ['white', 'gray'],
          },
          defaultValue: 'white',
        },
      },
    };
    
    ...
    ...
    
    
    const Template = args => <MyComponent {...args} />;
    
    export const Primary = Template.bind({});
    Primary.args = {
      ...props,
    };
    ...
    
    import { storybookCustomArgs, createStoryOptions } from 'src/lib/helpers';
    
    
    const props = BulletedOverviewComposition(Data);
    
    const itemsToShow = createStoryOptions(props.items);
    
    export default {
      title: 'Components/Bulleted Overview',
      component: BulletedOverview,
      argTypes: {
        ...storybookCustomArgs(props, BulletedOverview, [], false),
        items: {
          name: 'How many items?',
          control: {
            type: 'select',
            options: {
              ...itemsToShow,
            },
          },
        },
      },
    };
    
    ...
    looks like this: (note - only a few fields are shown as an example here)
    and return this array back to
    <SingleStepForm />
  • This function uses a Typescript overload to determine the correct return type depending on which multi boolean is passed in

  • inputMap is an object with field names as a key and their dataMap type as the value. These are basically fields that we've determined to be non-input type fields, such as select, checkbox, heading, etc... or fields that are hidden or unimportant

    validations: Will either be null or an object that contains if it should show on the confirmation screen and the validationPattern if any

    inside <FieldWrapper /> it will nest the dynamic <Component /> that was returned and will pass in some props, those are:

    • data: The data object returned from createForm()

    • errors: This is an object created by react-hook-form that contains key/value pairs of the field name and the error message

    • name: The name of the field from the data object. react-hook-form requires the name prop to exist at this point

    • props: The props object returned from createForm()

    • register: A function from react-hook-form that will eventually get passed all the way down to the input to be called on that input's ref. It has:

      • pattern: A regex pattern matching validation

      • required: A boolean or error message

    for example. Each file will have a Form[Input] component with its related Input component. This Input component will then be used in tests and storybook stories.
    {
      componentName: 'ContentMain',
      content: [{
          uid: '',
          componentName: 'Single Step Form',
          dataSource: '',
          fields: {
              ModelJson: {
                  value: '[ { "title":"Form Name", "fields":{ "Name":{ "label":"Form Name", "type":"input", "value":"Form Name", "name":"Name" }, "Id":{ "label":"Id", "type":"hidden", "value":"", "name":"Id" } }, "fresh":true }, { "title":"Text Input", "fields":{ "FormId":{ "label":"FormId", "type":"hidden", "value":"stepOne", "name":"FormId" }, "Name":{ "label":"Name", "type":"hidden", "value":"textinput", "name":"Name" }, "Id":{ "label":"Id", "type":"input", "value":"fixtureName", "name":"Id" }, "Label":{ "label":"Label", "type":"input", "value":"Fixture Selection", "name":"Label" }, "BackEndLabel":{ "label":"BackEnd Label", "type":"input", "value":"Fixture Selection", "name":"BackEndLabel" }, "Value":{ "label":"Default Value", "type":"input", "value":"", "name":"Value" }, "DefaultValueSource":{ "label":"Default Value Source", "type":"select", "value":[ { "value":"None", "selected":false, "label":"None" }, { "value":"cookie", "selected":true, "label":"Cookie" } ], "name":"DefaultValueSource" }, "DefaultValueKey":{ "label":"Default Value Source Key", "type":"input", "value":"fixtureName", "name":"DefaultValueKey"}]
              }
          }
      }]
    }
    - `createdFields` is memoized to cache the original value and keep it from re-rendering each cycle
    - The `false` parameter signifies that its not to return a multidimensional array, just a single array
    
    5. `createFormInit()` is a 'factory' for both `<SingleStepForm />` and `<MultiStepForm />`. We will focus on what happens if this is called from `<SingleStepForm />`.
    
    ```typescript
    
    const createFormInit: {
      (arr: Array<ParsedFormModel>, multi: true): CFReturnType[][];
      (arr: Array<ParsedFormModel>, multi: false): CFReturnType[];
    } = (arr: Array<ParsedFormModel>, multi: boolean): any => {
      return multi ? multiStepFormFields(arr) : parseFields(arr);
    };
    const parseFields = (arr: Array<ParsedFormModel>) => {
    
    const items = arr.reduce((acc: Array<CFReturnType | null>, curr) => {
    
    const formField = createForm(curr);
        return [...acc, formField];
      }, []);
    
    // filter out any null values due to early returns from hidden fields
      return items.filter(Boolean) as Array<CFReturnType>;
    };
    - `dataMap`
    
      - `dataMap` is an object with `inputMap` values as the key and related props as the value. All of these will contain a 'file' value. This is the name of the React component that will be dynamically imported, and some of them will contain a 'props' value. Props is an object that contains more details for that particular input type such as type, icon, masking function etc..
    
        ```javascript
    
        ```
    
    const dataMap: CFMappingType["dataMap"] = {
    
    // ...
    input: {
    file: "Input",
    props: { type: "text" },
    },
    phone: {
    file: "Input",
    props: {
    type: "tel",
    icon: "phone",
    mask: masks.tel,
    },
    },
    radio: {
    file: "RadioGroup",
    },
    
    // ...
    };
    2. Inside `createForm()` it will take the incoming field name and use it to index `inputMap`. At this point one of three things will happen:
       - It will find a match and return that input type
       - It will return a null value (these are currently hidden fields or unimportant fields)
       - It will return undefined\
         \
         If it returns a type we will continue, if it returns a null we return a null out of createForm(), if its undefined we then assume and assign this field with a type 'input'
    3. Now that we have the field type (the `inputType` value), we use this to index `dataMap` and return the file and or props from that
    
       - We take this file name and dynamically import that component (ie: 'Input', 'Heading', 'RadioGroup', 'Tabs', etc..)
    
       ```javascript
    4. We then now build our return object which gets added to the array in `parseFields()` and gets sent back to `<SingleStepForm />`
    
    ```javascript
    return {
      Component,
      data: getData(fields, title),
      file,
      formName: fields?.FormId?.value,
      id: uid(32),
      props: {
        columns: getColumnWidth(fields.ColumnWidth?.value),
        ...props,
      },
      validations: getValidations(file, fields, props?.type),
    };
    const getData: GetDataProps = ({ fields, title }) => ({
      customValidationErrorMsg:
        fields?.CustomValidationErrorMsg?.value || "field is required",
      items: parseItems(fields?.InputItems?.value),
      label: fields?.Label?.value || "",
      maxLength: parseInt(fields?.MaximumLength?.value) || 524288,
      minLength: parseInt(fields?.MinimumLength?.value) || 0,
      name: fields?.Name?.value,
      placeholder: fields?.PlaceholderText?.value,
      required: fields?.Required?.value || false,
      tabs: fields?.Tabs?.value.split("\n") || [],
      title,
      toolTipText: fields?.TooltipText?.value,
    });
    const getValidations: GetValidationProps = (file, fields, regex = "") => {
      const skipValidation = ["Heading", "Recaptcha", "Tabs"];
      let pattern;
    
      if (file && skipValidation.includes(file)) return null;
    
      // Validations will usually come through as a string value from sitecore
    
      // but can also come through as an array of objects, these have the type 'select'
    
      // We first need to parse through this array and grab the value of the selected validation pattern
      if (fields?.ValidationPattern?.type === "select") {
        pattern = getSelectedValue(fields.ValidationPattern.value);
      } else {
        pattern = fields?.ValidationPattern?.value;
      }
    
      return {
        shouldConfirm: fields?.AppearsOnFormConfirmation?.value,
        validationPattern:
          // 1. by regex in mapping props (phone, ssn)
    
          // 2. by named regex pattern coming from Sitecore
          regexMap[regex] || regexMap[pattern],
      };
    };
       return (
         <div className="relative">
           <InputText {...propData} />
           <div className="hidden lg:block absolute top-0 right-0 mt-4 lg:-mr-48">
             <Tooltip error={Boolean(errors[name])} message={toolTipText} />
           </div>
         </div>
       );
     };
     ```
    [[form stepper element], [...first section fields], [...second section fields] ...etc]
    const formModel: Array<ParsedFormModel> = JSON.parse(modelJson.value);
    
    const createdFields = useMemo(() => createFormInit(formModel, true), []);
    
    const createdFieldsWithoutTabs = [...createdFields.slice(1), []];
    
    const createdFieldsOnlyTabFields = [
      ...createdFields[0][0].data.tabs,
      "Confirmation",
    ];
    
    // ... return
    <FormComponent onSubmit={handleFormSubmit}>
      <FormStepper activeIndex={activeIndex} content={createdFieldsOnlyTabFields} />
      {createdFieldsWithoutTabs.map((innerArray, index) =>
        innerArray.map(
          ({ Component, data, id, props, validations, ...rest }: CFReturnType) => {
            return (
              <FieldWrapper
                className={index === activeIndex ? "block" : "hidden"}
                columns={props?.columns}
                key={id}
              >
                <Component
                  register={register({
                    pattern: regexPattern(validations),
                    required: isRequired(data),
                    validate: {
                      match: (value: string) =>
                        matchingEmails(
                          name.toLowerCase(),
                          value,
                          getValues(["email", "emailconf"])
                        ),
                    },
                  })}
                  {...{ data, errors, name, props, ...rest }}
                />
              </FieldWrapper>
            );
          }
        )
      )}
      {isLastStep && <ConfirmationStep data={confirmedValues} {...{ fields }} />}
      <ButtonWrapper />
    </FormComponent>;
    [
      {
        "title": "Radio List",
        "fields": {
          "FormId": {
            "label": "FormId",
            "type": "hidden",
            "value": "stepOne",
            "name": "FormId"
          },
          "Name": {
            "label": "Name",
            "type": "input",
            "value": "Are you a customer requesting a single light for your yard or property?",
            "name": "Name"
          },
          "Label": {
            "label": "Label",
            "type": "input",
            "value": "Are you a customer requesting a single light for your yard or property?",
            "name": "Label"
          },
          "BackEndLabel": {
            "label": "BackEnd Label",
            "type": "input",
            "value": "Are you a customer requesting a single light for your yard or property?",
            "name": "BackEndLabel"
          },
          "GroupName": {
            "label": "Group Name",
            "type": "input",
            "value": "GroupName",
            "name": "GroupName"
          },
          "Value": {
            "label": "Default Value",
            "type": "input",
            "value": "",
            "name": "Value"
          },
          "Id": {
            "label": "Id",
            "type": "input",
            "value": "radiolist",
            "name": "Id"
          },
          "TooltipText": {
            "label": "Tooltip Text",
            "type": "input",
            "value": "",
            "name": "TooltipText"
          },
          "CustomValidationErrorMsg": {
            "label": "Custom Validation Err. Msg.",
            "type": "input",
            "value": "Please pick an option.",
            "name": "CustomValidationErrorMsg"
          },
          "AppearsOnFormConfirmation": {
            "label": "Appear on Confirmation",
            "type": "checkbox",
            "value": true,
            "name": "AppearsOnFormConfirmation"
          },
          "Required": {
            "label": "Required",
            "type": "checkbox",
            "value": true,
            "name": "Required"
          },
          "InputItems": {
            "label": "Items",
            "type": "radiolist",
            "value": "[{\"text\":\"Yes\",\"value\":\"Yes\"},{\"text\":\"No\",\"value\":\"No\"}]",
            "name": "InputItems"
          }
        },
        "fresh": true
      },
      {
        "title": "Section Header",
        "fields": {
          "FormId": {
            "label": "FormId",
            "type": "hidden",
            "value": "stepOne",
            "name": "FormId"
          },
          "Name": {
            "label": "Name",
            "type": "hidden",
            "value": "sectionhdr",
            "name": "Name"
          },
          "Label": {
            "label": "Label",
            "type": "input",
            "value": "Name & Address",
            "name": "Label"
          },
          "BackEndLabel": {
            "label": "BackEnd Label",
            "type": "input",
            "value": "Name & Address",
            "name": "BackEndLabel"
          },
          "Id": {
            "label": "Id",
            "type": "hidden",
            "value": "sectionhdr",
            "name": "Id"
          }
        },
        "fresh": true
      },
      {
        "title": "First Name",
        "fields": {
          "FormId": {
            "label": "FormId",
            "type": "hidden",
            "value": "stepOne",
            "name": "FormId"
          },
          "Name": {
            "label": "Name",
            "type": "hidden",
            "value": "FirstName",
            "name": "Name"
          },
          "Id": {
            "label": "Id",
            "type": "hidden",
            "value": "FirstName",
            "name": "Id"
          },
          "Label": {
            "label": "Label",
            "type": "hidden",
            "value": "First Name",
            "name": "Label"
          },
          "BackEndLabel": {
            "label": "BackEnd Label",
            "type": "hidden",
            "value": "First Name",
            "name": "BackEndLabel"
          },
          "Value": {
            "label": "Default Value",
            "type": "hidden",
            "value": "",
            "name": "Value"
          },
          "ValidationPattern": {
            "label": "Validation Rule",
            "type": "hidden",
            "value": "lettersWhiteSpace",
            "name": "ValidationPattern"
          },
          "TooltipText": {
            "label": "Tooltip Text",
            "type": "input",
            "value": "",
            "name": "TooltipText"
          },
          "CustomValidationErrorMsg": {
            "label": "Custom Validation Err. Msg.",
            "type": "hidden",
            "value": "Please enter a first name.",
            "name": "CustomValidationErrorMsg"
          },
          "AppearsOnFormConfirmation": {
            "label": "Appear on Confirmation",
            "type": "checkbox",
            "value": false,
            "name": "AppearsOnFormConfirmation"
          },
          "Required": {
            "label": "Required",
            "type": "checkbox",
            "value": true,
            "name": "Required"
          },
          "MinimumLength": {
            "label": "Min. Length",
            "type": "hidden",
            "value": "",
            "name": "MinimumLength"
          },
          "MaximumLength": {
            "label": "Max. Length",
            "type": "hidden",
            "value": "40",
            "name": "MaximumLength"
          },
          "Predefined": {
            "label": "Predefined",
            "type": "hidden",
            "value": "true",
            "name": "Predefined"
          },
          "ColumnWidth": {
            "label": "Column Width",
            "type": "select",
            "value": [
              {
                "value": "2",
                "selected": false,
                "label": "2"
              },
              {
                "value": "3",
                "selected": true,
                "label": "3"
              },
              {
                "value": "4",
                "selected": false,
                "label": "4"
              },
              {
                "value": "5",
                "selected": false,
                "label": "5"
              },
              {
                "value": "6",
                "selected": false,
                "label": "6"
              }
            ],
            "name": "ColumnWidth"
          }
        },
        "fresh": true
      }
    ]
    <FormComponent onSubmit={handleSubmit(onSubmit)}>
      {createdFields.map(
        ({
          Component,
          data,
          id,
          props,
          validations,
          ...rest
        }: CFReturnType) => {
          return (
            <FieldWrapper columns={props?.columns} key={id}>
              <Component
                register={register({
                  pattern: regexPattern(validations),
                  required: isRequired(data),
                  validate: {
                    match: (value: string) =>
                      matchingEmails(
                        name.toLowerCase(),
                        value,
                        getValues(["email", "emailconf"])
                      ),
                  },
                })}
                {...{ data, errors, name, props, ...rest }}
              />
            </FieldWrapper>
          );
        }
      )}
      <Button type="submit" variant="primary">
        submit
      </Button>
    </FormComponent>

    validate: custom function that passes the field's value through a method that returns a boolean

    👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Intro

    hashtag
    Component Creation - Intro


    Table of Contents:

    1. Intro

    2. Technical Overview

    3. Practical Overview

    4. Definition of Done

    5. Sitecore


    hashtag
    Introduction

    To understand how a component is displayed within our application, it helps to understand the process by which it is rendered within our React app.

    This is a somewhat complex process, but the simplified overview is this:

    • When a user navigates to a route, the React app receives a big bundle of data from Sitecore, including a list of components, and the relevant data for those components.

    • That list represents the building blocks of the page that the user has navigated to.

    • Those "building blocks" are checked against the component list of our application, and when a match is found, a React component is returned, along with the data that Sitecore sent along with the component that it needs to render properly.

    Oops! 🙀 we dont have Global Alerts yet!\

    Let's dive in and get a little more insight.

    Next >

    Intro | Technical Overview | Practical Overview | Typescript | Sitecore | Definition of Done | Analytics

    If a match is not found, a "placeholder component" is rendered instead. Within the React app, this is exactly what it sounds like: a big, ugly "placeholder" to remind you that this component has not yet been created for the app:

    Typescript

    hashtag
    Global types

    In typescript global types can be declared in a .d.ts file and used anywhere without explicitly importing them. Our project's .d.ts file is named project.d.ts.

    chevron-rightproject.d.tshashtag

    It contains:

    1. Some library types in the form of [triple slash directives](https: //www.typescriptlang.org/docs/handbook/triple-slash-directives.html). These need to be placed at the top of the file.

    2. Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).

    3. Our own global types.

    Typescript provides many [Utility Types](https: //www.typescriptlang.org/docs/handbook/utility-types.html) which are useful for manipulating the base types in the global ComponentTypes interface.

    chevron-rightUtility Typeshashtag

    TypeScript: Documentation - Utility Types

    Excerpt

    Types which are globally included in TypeScript


    TypeScript provides several utility types to facilitate common type transformations. These utilities are available globally.

    A few basic ones to know:

    hashtag
    Pick<Type, Keys>

    Only use the specified Keys from the Type.

    hashtag
    Partial<Type>

    Allows the type to be optional (undefined)

    hashtag
    Required<Type>

    Opposite of Partial, the type must be defined

    Using the stategies above you can select types from the global source and compose them to create a representation of the props in a specific component. While the global types live in project.d.ts, component level types should generally be placed in a types.ts file within the component directory and imported for use.

    Although ComponentTypes is a ✅ _Good starting place, some components may require a type that is more specific and not usefully included in the global declaration._


    hashtag
    Naming


    hashtag
    class

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers/methods use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    enum

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    interface

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good

    For memebers use 🐪 camelCase

    🚫 Bad

    ✅ Good


    hashtag
    namespace

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ Good


    hashtag
    type

    🧑‍🔬 PascalCase

    🚫 Bad

    ✅ ✅ Good


    hashtag
    variable and function

    🐪 camelCase

    🚫 Bad

    ✅ Good


    React | Typescript | Tailwind | Forms | Unit Tests

    Partial<Type>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs a type with all properties of Type set to optional. This utility will return a type that represents all subsets of a given type.

    Example

    Required<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#improved-control-over-mapped-type-modifiers)

    constructs a type consisting of all properties of Type set to required. The opposite of [Partial](https: //www.typescriptlang.org/docs/handbook/utility-types.html#partialtype).

    Example

    Readonly<Type>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs a type with all properties of Type set to readonly, meaning the properties of the constructed type cannot be reassigned.

    Example

    This utility is useful for representing assignment expressions that will fail at runtime (i.e. when attempting to reassign properties of a [frozen object](https: //developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)).

    Object.freeze

    Record<Keys, Type>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs an object type whose property keys are Keys and whose property values are Type. This utility can be used to map the properties of a type to another type.

    Example

    Pick<Type, Keys>

    Released: [2.1](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#partial-readonly-record-and-pick)

    constructs a type by picking the set of properties Keys (string literal or union of string literals) from Type.

    Example

    Omit<Type, Keys>

    Released: [3.5](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type)

    constructs a type by picking all properties from Type and then removing Keys (string literal or union of string literals).

    Example

    Exclude<UnionType, ExcludedMembers>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type by excluding from UnionType all union members that are assignable to ExcludedMembers.

    Example

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type by extracting from Type all union members that are assignable to Union.

    Example

    NonNullable<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type by excluding null and undefined from Type.

    Example

    Parameters<Type>

    Released: [3.1](https: //github.com/microsoft/TypeScript/pull/26243)

    constructs a tuple type from the types used in the parameters of a function type Type.

    Example

    `

    constructorParameters`

    Released: [3.1](https: //github.com/microsoft/TypeScript/pull/26243)

    constructs a tuple or array type from the types of a constructor function type. It produces a tuple type with all the parameter types (or the type never if Type is not a function).

    Example

    ReturnType<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type consisting of the return type of function Type.

    Example

    InstanceType<Type>

    Released: [2.8](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-2-8.html#predefined-conditional-types)

    constructs a type consisting of the instance type of a constructor function in Type.

    Example

    ThisParameterType<Type>

    Released: [3.3](https: //github.com/microsoft/TypeScript/pull/28920)

    Extracts the type of the [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this-parameters) parameter for a function type, or [unknown](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#new-unknown-top-type) if the function type has no this parameter.

    Example

    OmitThisParameter<Type>

    Released: [3.3](https: //github.com/microsoft/TypeScript/pull/28920)

    Removes the [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this-parameters) parameter from Type. If Type has no explicitly declared this parameter, the result is simply Type. Otherwise, a new function type with no this parameter is created from Type. Generics are erased and only the last overload signature is propagated into the new function type.

    Example

    ThisType<Type>

    Released: [2.3](https: //github.com/microsoft/TypeScript/pull/14141)

    This utility does not return a transformed type. Instead, it serves as a marker for a contextual [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this) type. Note that the [noImplicitThis](https: //www.typescriptlang.org/tsconfig#noImplicitThis) flag must be enabled to use this utility.

    Example

    In the example above, the methods object in the argument to makeObject has a contextual type that includes ThisType<D & M> and therefore the type of [this](https: //www.typescriptlang.org/docs/handbook/functions.html#this) in methods within the methods object is { x: number, y: number } & { moveBy(dx: number, dy: number): number }. Notice how the type of the methods property simultaneously is an inference target and a source for the this type in methods.

    The ThisType<T> marker interface is simply an empty interface declared in lib.d.ts. Beyond being recognized in the contextual type of an object literal, the interface acts like any empty interface.

    Intrinsic String Manipulation Types

    Uppercase<StringType>

    Lowercase<StringType>

    Capitalize<StringType>

    Uncapitalize<StringType>

    To help with string manipulation around template string literals, TypeScript includes a set of types which can be used in string manipulation within the type system. You can find those in the [Template Literal Types](https: //www.typescriptlang.org/docs/handbook/2/template-literal-types.html#uppercasestringtype) documentation.

    enum GlobalConfigKey {
      ApigeeServiceHost,
      ApigeeServiceHostCC,
      ApiHost,
      AppInsightsKey,
      SITECORE_API_KEY,
      GoogleTagManagerId,
    }
    
    type GlobalConfig = {
      [key in keyof typeof GlobalConfigKey]?: string;
    };
    
    interface Window {
      globalConfig: GlobalConfig;
      angular?: any;
    }
    
    declare let globalConfig: GlobalConfig;
    
    declare namespace JSS {
      import("@sitecore-jss/sitecore-jss-react");
      import { Text } from "@sitecore-jss/sitecore-jss-react";
    
      type Field<
        Component extends (...args: any) => any,
        Key extends Parameters<Component>[0] = "field"
      > = Pick<Parameters<Component>[0], Key>[Key];
    
      export type LinkField = {
        value: {
          [K in
            | "href"
            | "id"
            | "querystring"
            | "text"
            | "title"
            | "target"
            | "class"
            | "url"
            | "linktype"]?: string;
        };
        editable?: string;
        editableFirstPart?: string;
        editableLastPart?: string;
      };
    
      type ImageField = {
        value?: {
          [K in "alt" | "height" | "src" | 
        };
        editable?: string;
      };
    
      type TextField = Field<typeof Text>;
    
      export type { LinkField, ImageField, TextField };
    }
    tsinterface Todo {  title: string;  description: string;}function updateTodo(todo: Todo, fieldsToUpdate: Partial<Todo>) {  return { ...todo, ...fieldsToUpdate };}
    const todo1 = {  title: "organize desk",  description: "clear clutter",};
    const todo2 = updateTodo(todo1, {  description: "throw out trash",});Try
    tsinterface Props {  a?: number;  b?: string;}
    const obj: Props = { a: 5 };
    const obj2: Required<Props> = { a: 5 };Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.2741Property 'b' is missing in type '{ a: number; }' but required in type 'Required<Props>'.Try
    tsinterface Todo {  title: string;}
    const todo: Readonly<Todo> = {  title: "Delete inactive users",};todo.title = "Hello";Cannot assign to 'title' because it is a read-only property.2540Cannot assign to 'title' because it is a read-only property.Try
    tsfunction freeze<Type>(obj: Type): Readonly<Type>;
    tsinterface CatInfo {  age: number;  breed: string;}type CatName = "miffy" | "boris" | "mordred";
    const cats: Record<CatName, CatInfo> = {  miffy: { age: 10, breed: "Persian" },  boris: { age: 5, breed: "Maine Coon" },  mordred: { age: 16, breed: "British Shorthair" },};cats.boris;
    const cats: Record<CatName, CatInfo>Try
    tsinterface Todo {  title: string;  description: string;  completed: boolean;}type TodoPreview = Pick<Todo, "title" | "completed">;
    const todo: TodoPreview = {  title: "Clean room",  completed: false,};todo;
    const todo: TodoPreviewTry
    tsinterface Todo {  title: string;  description: string;  completed: boolean;  createdAt: number;}type TodoPreview = Omit<Todo, "description">;
    const todo: TodoPreview = {  title: "Clean room",  completed: false,  createdAt: 1615544252770,};todo;
    const todo: TodoPreviewtype TodoInfo = Omit<Todo, "completed" | "createdAt">;
    const todoInfo: TodoInfo = {  title: "Pick up kids",  description: "Kindergarten closes at 5pm",};todoInfo;
    const todoInfo: TodoInfoTry
    tstype T0 = Exclude<"a" | "b" | "c", "a">;     type T0 = "b" | "c"type T1 = Exclude<"a" | "b" | "c", "a" | "b">;     type T1 = "c"type T2 = Exclude<string | number | (() => void), Function>;     type T2 = string | numberTry
    tstype T0 = Extract<"a" | "b" | "c", "a" | "f">;     type T0 = "a"type T1 = Extract<string | number | (() => void), Function>;     type T1 = () => voidTry
    tstype T0 = NonNullable<string | number | undefined>;     type T0 = string | numbertype T1 = NonNullable<string[] | null | undefined>;     type T1 = string[]Try
    tsdeclare function f1(arg: { a: number; b: string }): void;type T0 = Parameters<() => string>;     type T0 = []type T1 = Parameters<(s: string) => void>;     type T1 = [s: string]type T2 = Parameters<<T>(arg: T) => T>;     type T2 = [arg: unknown]type T3 = Parameters<typeof f1>;     type T3 = [arg: {
        a: number;
        b: string;
    }]type T4 = Parameters<any>;     type T4 = unknown[]type T5 = Parameters<never>;     type T5 = nevertype T6 = Parameters<string>;Type 'string' does not satisfy the
    constraint '(...args: any) => any'.2344Type 'string' does not satisfy the
    constraint '(...args: any) => any'.     type T6 = nevertype T7 = Parameters<Function>;Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.2344Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.     type T7 = neverTry
    tstype T0 =
    constructorParameters<Error
    constructor>;     type T0 = [message?: string]type T1 =
    constructorParameters<Function
    constructor>;     type T1 = string[]type T2 =
    constructorParameters<RegExp
    constructor>;     type T2 = [pattern: string | RegExp, flags?: string]type T3 =
    constructorParameters<any>;     type T3 = unknown[]type T4 =
    constructorParameters<Function>;Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
      Type 'Function' provides no match for the signature 'new (...args: any): any'.2344Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
    
    tsdeclare function f1(): { a: number; b: string };type T0 = ReturnType<() => string>;     type T0 = stringtype T1 = ReturnType<(s: string) => void>;     type T1 = voidtype T2 = ReturnType<<T>() => T>;     type T2 = unknowntype T3 = ReturnType<<T extends U, U extends number[]>() => T>;     type T3 = number[]type T4 = ReturnType<typeof f1>;     type T4 = {
        a: number;
        b: string;
    }type T5 = ReturnType<any>;     type T5 = anytype T6 = ReturnType<never>;     type T6 = nevertype T7 = ReturnType<string>;Type 'string' does not satisfy the
    constraint '(...args: any) => any'.2344Type 'string' does not satisfy the
    constraint '(...args: any) => any'.     type T7 = anytype T8 = ReturnType<Function>;Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.2344Type 'Function' does not satisfy the
    constraint '(...args: any) => any'.
      Type 'Function' provides no match for the signature '(...args: any): any'.     type T8 = anyTry
    tsclass C {  x = 0;  y = 0;}type T0 = InstanceType<typeof C>;     type T0 = Ctype T1 = InstanceType<any>;     type T1 = anytype T2 = InstanceType<never>;     type T2 = nevertype T3 = InstanceType<string>;Type 'string' does not satisfy the
    constraint 'abstract new (...args: any) => any'.2344Type 'string' does not satisfy the
    constraint 'abstract new (...args: any) => any'.     type T3 = anytype T4 = InstanceType<Function>;Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
      Type 'Function' provides no match for the signature 'new (...args: any): any'.2344Type 'Function' does not satisfy the
    constraint 'abstract new (...args: any) => any'.
      Type 'Function' provides no match for the signature 'new (...args: any): any'.     type T4 = anyTry
    tsfunction toHex(this: Number) {  return this.toString(16);}function numberToString(n: ThisParameterType<typeof toHex>) {  return toHex.apply(n);}Try
    tsfunction toHex(this: Number) {  return this.toString(16);}
    const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);console.log(fiveToHex());Try
    tstype ObjectDescriptor<D, M> = {  data?: D;  methods?: M & ThisType<D & M>;
    // Type of 'this' in methods is D & M};function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {  let data: object = desc.data || {};  let methods: object = desc.methods || {};  return { ...data, ...methods } as D & M;}let obj = makeObject({  data: { x: 0, y: 0 },  methods: {    moveBy(dx: number, dy: number) {      this.x += dx;
    // Strongly typed this      this.y += dy;
    // Strongly typed this    },  },});obj.x = 10;obj.y = 20;obj.moveBy(5, 5);Try
    Pick<ComponentTypes, "text">;
    
    // only use 'text' type
    Partial<Pick<ComponentTypes, "text">>;
    
    // only use 'text' type
    
    // the text type is optional
    Required<Pick<ComponentTypes, "text">>;
    
    // only use 'text' type
    
    // the text type is required
    {['class', 'enum', 'interface', 'namespace', 'type', 'variable-and-function'].map(item => (
    {item.split('-').join(' ')}
    ))}
    class foo {}
    class Foo {}
    class Foo {
      Bar: number;
      BazQux() {}
    }
    class Foo {
      bar: number;
      bazQux() {}
    }
    enum backgroundColor {}
    enum BackgroundColor {}
    interface checkboxProps {}
    interface CheckboxProps {}
    interface CheckboxProps {
      IsSelected: boolean;
    }
    interface CheckboxProps = {
      isSelected: boolean;
    }
    namespace foo {}
    namespace Foo {}
    type imageProps = { src: string; alt: string };
    type ImageProps = { src: string; alt: string };
    const FooBar = "baz";
    
    const FooBar = () => "baz";
    const fooBar = "baz";
    
    const fooBar = () => "baz";
    "
    width
    "
    ]
    ?:
    string
    ;
    Type 'Function' provides no match for the signature 'new (...args: any): any'. type T4 = neverTry