New Simple Calculator Input Warning And Block Explanation**
The need for this ticket derives from a fictional unit of energy duke created called a block which is "100 kWh of 'green' energy". Because a block requires whole-number outputs that necessitates a minimum input value; which arguably needs to be explained to the user, and if the user fails to heed the instructions... have the warning change color to red if they input a lower number.
The information about call-time preference is visually displayed as a description list. In this case, it's a question and then an answer. This content is not marked up as a list.
When content is presented as a list, or a clearly related collection of items, marking it up using semantic list HTML elements helps assistive technology users to navigate and orient themselves within a page of content. Screen reader users can navigate to (or over) lists when they are correctly marked up. Also, when reading content in a list, assistive technology provides rich information about how long the list is and where the user is in the list. For example: "List, 9 items, Home, 1 of 9, About, 2 of 9".
This particular list is confusing because it is displayed even when there is no time indicated, like when a user chooses email as the preferred method of communication.
Site Navigation
Multi Step Forms
DNT-2658 Components must receive focus in an order that preserves meaning and operability
DNT-2658
Components must receive focus in an order that preserves meaning and operability
Additionally the calculator should include an explanation of the fictional unit being used in the output
i.e.
When you participate in the GoGreen Ohio program, you’re supporting the advancement of green power sources. Green power is electricity produced from natural resources – like the sun, wind, and water – that do not emit pollutants into the atmosphere.
As a GoGreen Ohio participant, you can match your home’s electricity use by purchasing blocks* of renewable energy certificates (RECs). These RECs certify the generation of green power on your behalf. Every block you purchase supports the advancement of environmentally friendly, renewable energy sources, thereby reducing dependence on fossil fuels to generate energy.
*One GoGreen Ohio program block represents 100 kWh of clean energy. A 10-block purchase (1,000 kWh) equals one REC (1MWh).
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.
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:
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.
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):
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.
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.
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.
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:
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
Remove the list entirely and write this information as a sentence. "We will reach out to you by phone during business hours," for example. If the user indicates that they prefer email, either write a new sentence indicating that preference, "One of our representatives will email you shortly," or remove the sentence altogether.
If you choose to keep the question and answer structure, use a description list (<dl>)for this data.
See Understanding Techniques for WCAG Success Criteria for important information about the usage of these informative techniques and how they relate to the normative WCAG 2.1 success criteria. The Applicability section explains the scope of the technique, and the presence of techniques for a specific technology does not imply that the technology can be used in all situations to create content that meets WCAG 2.1.
Applicability
HTML and XHTML
This technique relates to (Sufficient when used with ).
Description
The objective of this technique is to provide the description of names or terms by presenting them in a description list. The list is marked up using the dl element. Within the list, each term is put in a separate dt element, and its description goes in the dd element directly following it. Multiple terms can be associated with a single description, as can a single term with multiple descriptions, provided that semantic sequence is maintained. The title attribute can be used to provide additional information about the description list. Usage of description lists ensures that terms and their descriptions are semantically related even as presentation format changes, as well as ensuring that these terms and descriptions are semantically grouped as a unit.
Description lists are easiest to use when the descriptions are ordered alphabetically. A common use for description lists is a glossary of terms.
NOTE
In HTML5 the name "Description list" was introduced. In previous versions these lists were referred to as "Definition lists".
Examples
Example 1
A list of descriptions of nautical terms used on a Website about sailing.
Resources
Resources are for information purposes only, no endorsement implied.
Related Techniques
Tests
Procedure
For any set of terms and their associated descriptions:
Check that the list is contained within a dl element.
Check that each term in the list being described is contained within a dt element.
Check that when there is more than one term that shares the same decription that the dt
Features a full-width image (light or dark) with superimposed text (light or dark contrasting). See a demo page with a site header.
Usage Guidance
Do
Use on home pages and landing pages with multiple child pages.
When used on pages other than Home Pages or Section Landing Pages, the headline (H1) must be the page title.
Include a title/headline (required), a description (optional), and call-to-action link button(s) (optional).
Do Not
Use more than ONE Hero on a page.
Toggle Component Options
Theme Black to Gray-Darker Blue-Dark to Blue Blue to Teal-Darker Green-Dark to Green Blue-Dark to Green-Dark Green-Dark to Blue-Dark Gray-Light to white Gray-Light to Gray-Lighter Teal-Light to Teal-Lighter Image Business Solar Chef Family Landscape One Person Store Solar Solar Cell Two Person Store Content Content 1 Content 2 Content 3 Too Much Content Action No Actions Primary Action Primary and Secondary Action
Say Yes to Renewables
Striving for sustainability is just smart business
Accessibility
Labeling Expectations
Use a unique `id=""` on the title.
Use a unique `id=""` on the action link button(s).
Use `aria-labelledby=""` on the link buttons referencing the id of the link button(s) and the id of the title.
Keyboard Expectations
When you TAB into the `Hero` component, focus will go onto the link button(s).
Using Throttling and Debouncing with React hooks - DEV Community
Excerpt
Throttling and debouncing techniques has been in use for past many years in javascript.
In this post I'd like to share my knowledge on how we can use throttle and debounce functions with help of react hooks.
Consider below example with two routes / and /count rendering respective components.
Throttling Example with useEffect
Suppose we need to subscribe a scroll event on Count component on its mount and just increment the count on every scroll event.
Code without using throttle or debounce techniques will be like:
Suppose in practical applications you need to use throttle and wait for every 100ms before we execute increaseCount. I have used the lodash throttle function for this example.
Wait, no need to hurry. It will work if you are at /count route. The increaseCount function will be throttled and will increase the count after 100ms of intervals.
But as you move to the / route to render the Home component and unmount the Count component, and start scrolling on home page, you will notice a warning in console which warns about memory leak. This is probably because the scroll event was not cleaned properly.
The reason is _.throttle(increaseCount, 100) is called again during unmount and returns another function which does not match that created during the mount stage.
What if we create a variable and store the throttled instance.
like this
But it has problem too. The throttledCount is created on every render, which is not at all required. This function should be initiated once which is possible inside the useEffect hook. As it will now be computed only once during mount.
Debounce Example using useCallback or useRef
Above example is pretty simple. Let's look at another example where there is an input field and you need to increment the count only after user stops typing for certain time. And there is text which is updated on every keystroke which re renders the component on every input.
Code with debounce:
This will not work. The count will increase for every keystroke. The reason behind is that on every render, a new debouncedCount is created.
We have to store this debounced function such that it is initiated only once like that in useEffect in above example.
Here comes use of useCallback.
useCallback will return a memoized version of the callback that only changes if one of the dependencies has changed - React docs
Replace
with
and it will work. Because this time the function is evaluated only once at the initial phase.
Or we can also use useRef
by doing this
One should always keep in mind that every render call of react functional component will lead to expiration of local variables and re-initiation unless you memoize them using hooks.
import React, { useRef, PropsWithChildren } from 'react';
import { createPortal } from 'react-dom';
import { a11yAction } from 'src/lib/helpers';
import { Placeholder } from '@sitecore-jss/sitecore-jss-react';
import SvgLoader from 'src/components/SvgLoader';
import Transition from 'src/lib/Transition';
import { useBodyContext } from 'src/components/ContentWrapper/context';
import { useExperienceEditor } from 'src/lib/useExperienceEditor';
import useMediaQuery from 'src/lib/useMediaQuery';
import usePreventBodyScroll from 'src/lib/usePreventBodyScroll';
import { ModalComponentTypes, ModalTypes } from './types';
import { useFocusTrap } from 'src/lib/useFocusTrap';
Code Review:
Created by Anonymous, last modified on
"Everybody is junior at something", or so they say. You may be fairly early on in your career but have some experience working in a large enterprise or business where structured Code Reviews were the order of the day. Or you may have many years experience on a loose team that didn't really take Code Review very seriously. Thus, you may find that while you are a senior developer, you are "junior" (I honestly hate that term) when it comes to Code Reviews, and vice versa. Whatever the case, Ninja Turtles values Code Review.
DNT-2659 A meaningful image must have a text alternative that serves the equivalent purpose
Info
Duke Company
Portal Guide - Home
Excerpt
The Modern Portal Project Is Complete! »
Personal
Two External Monitors (Macbook Air)
Hi, I have a macbook air... is it at all possible for me to find a workaround that allows f
Incident ID: INC000032293420 Last Modified Date: 11 Mar 2022 18:59:06 UTC Assignee:
Hi ,
DNT-2724 Accordion Testing
5 Things You Didn't Know About React Testing Library
Excerpt
I've just sold myself to the gods of click-baiting by making an "x things you didn't know about y" post. But hey, at least there no subtitle that says "number three will blow your mind!"
I've just sold myself to the gods of click-baiting by making an "x things you didn't know about y" post. But hey, at least there no subtitle that says "number three will blow your mind!"
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/NC Solar Rebates/Content/Multi Column main 2018/Related Links right sidebar
Previous Related Links
8/16/2021 3:20:36 PM
sitecore\courtney.icenhour
8/16/2021 3:12:37 PM
sitecore\courtney.icenhour
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/NC GreenPower/Content/Previous Related Links
Previous Related Links
8/16/2021 4:33:16 PM
sitecore\courtney.icenhour
8/16/2021 4:32:38 PM
sitecore\courtney.icenhour
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Previous Related Links
Previous Related Programs KYOHIN
8/16/2021 4:33:18 PM
sitecore\courtney.icenhour
8/16/2021 4:32:28 PM
sitecore\courtney.icenhour
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Previous Related Programs KYOHIN
IN Annual
9/13/2016 6:39:06 PM
nam\jholly1
9/13/2016 6:39:01 PM
nam\jholly1
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/IN/IN Annual
KY Annual
9/13/2016 6:40:38 PM
nam\jholly1
9/13/2016 6:40:23 PM
nam\jholly1
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/KY/KY Annual
OH Annual
9/13/2016 6:40:51 PM
nam\jholly1
9/13/2016 6:40:43 PM
nam\jholly1
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/GoGreen Energy/Content/Content from Bus/OH/OH Annual
Related Links
4/1/2021 1:38:35 AM
sitecore\van.parker
8/1/2016 1:51:51 PM
NAM\AValaka
/sitecore/content/JssPublic/Home/Home/Products/Renewable Energy/Generate Your Own/Content/Unused/Related Links
RL Related links
4/10/2017 9:51:20 AM
nam\rvp0696
3/3/2017 1:03:24 PM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas/East End Replacement Project/Content/MC Main Copy/RL Related links
RL Gas Main Replacement
9/26/2016 11:14:13 AM
NAM\KMcCart
6/7/2016 4:59:35 PM
NAM\KMcCart
/sitecore/content/JssPublic/Home/Home/Natural Gas/Gas Main Replacement/Content/RL Gas Main Replacement
RLinks Proj Street List
11/16/2019 2:17:39 PM
sitecore\admin
7/7/2016 1:16:22 PM
NAM\KMcCart
/sitecore/content/JssPublic/Home/Home/Natural Gas/Gas Main Replacement/Content/RLinks Proj Street List
Related Links
3/24/2017 4:12:44 PM
nam\rvp0696
2/27/2017 12:47:26 PM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links
RL Gas line integrity
11/16/2019 2:17:40 PM
sitecore\admin
2/27/2017 12:42:01 PM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links/RL Gas line integrity
RL Gas line integrity
11/16/2019 2:17:40 PM
sitecore\admin
2/27/2017 12:37:49 PM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas/Natural Gas Pipeline Integrity/Content/MC Main Content/Related Links/RL Gas line integrity
Related Links right col
1/6/2020 8:37:36 PM
sitecore\Van.Parker
12/30/2019 5:15:13 PM
sitecore\Van.Parker
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/4th Street Replacement/Content/Main Content Container/Related Links right col
Related Links
12/16/2020 9:19:37 PM
sitecore\courtney.icenhour
1/16/2020 2:37:58 PM
sitecore\Van.Parker
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Delhi Township Infrastructure/Content/Multi Column main/Related Links
Related Links
1/16/2020 3:52:13 PM
sitecore\Van.Parker
1/16/2020 2:37:58 PM
sitecore\Van.Parker
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Roundbottom Road/Content/Main Content Container/Related Links
Neighborhood Maps East
11/16/2019 2:54:54 PM
sitecore\admin
10/10/2019 9:59:05 AM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Central Corridor Pipeline 2/Content/Map Content East/Neighborhood Maps East
Neighborhood Maps West
11/16/2019 2:54:55 PM
sitecore\admin
10/10/2019 10:56:48 AM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Central Corridor Pipeline 2/Content/Map Content West/Neighborhood Maps West
Related Links
3/24/2021 1:26:54 PM
sitecore\van.parker
2/5/2019 11:16:47 AM
NAM\CBIcenh
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Line A Replacement/Content/Main Content/Related Links
Related Links
3/25/2021 1:36:48 PM
sitecore\van.parker
2/5/2019 11:17:37 AM
NAM\CBIcenh
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Content/Unused/Winton Rd Replacement Project/Content/Multi Column New/Related Links
Related Links
10/29/2019 3:52:31 PM
NAM\CBIcenh
10/25/2019 12:16:29 PM
NAM\CBIcenh
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Boone County Pipeline/Content/MC main/Related Links
Related Links
2/17/2022 6:46:27 PM
sitecore\william.natta
2/9/2022 9:40:36 PM
sitecore\william.natta
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Butler County Upgrade Project/Content/MC Body/Related Links
Related Links
3/18/2022 3:13:44 PM
sitecore\william.natta
3/11/2022 1:39:25 PM
sitecore\william.natta
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Milford Replacement Project/Content/MC Body/Related Links
Neighborhood Maps West
1/14/2020 8:46:05 PM
sitecore\Van.Parker
10/10/2019 10:56:48 AM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Central Corridor Pipeline Ext/Content/Map Content West/Neighborhood Maps West
Letters of Support
7/26/2017 11:23:06 AM
nam\rvp0696
7/20/2017 4:49:09 PM
nam\rvp0696
/sitecore/content/JssPublic/Home/Home/Natural Gas Projects/Central Corridor Pipeline Ext/Project Summary/Content/Multi Column/Letters of Support
Related Links pod 1 right
8/18/2016 1:35:20 PM
default\Anonymous
6/16/2016 10:55:20 AM
nam\avalaka
/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/pod 1 cleaner/Multi Column pod 1/Related Links pod 1 right
Related Links right 2
7/15/2016 2:07:53 PM
NAM\SHaberm
6/17/2016 1:20:48 PM
nam\avalaka
/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/pod item 2 more reliable/Multi Column 1/Related Links right 2
Related Links right 3
7/15/2016 2:07:56 PM
NAM\SHaberm
6/17/2016 1:46:44 PM
nam\avalaka
/sitecore/content/JssPublic/Home/Home/Campaigns/Power For Your Life/Content/Push Down Panel/Pod 3/Multi Column pod 3/Related Links right 3
Related Links IN OH KY
2/25/2021 3:53:21 PM
sitecore\courtney.icenhour
11/9/2016 6:08:16 PM
NAM\cball
/sitecore/content/JssPublic/Home/Business/Billing/Paperless/Large Business Paperless/Content/Related Links IN OH KY
Related Links DEC DEP NC SC FL
2/25/2021 3:53:25 PM
sitecore\courtney.icenhour
2/25/2021 3:47:54 PM
sitecore\courtney.icenhour
/sitecore/content/JssPublic/Home/Business/Billing/Paperless/Large Business Paperless/Content/Related Links DEC DEP NC SC FL
/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators IN DEC DEP/Custom/Multi Column classic/Related Links right
Related Links NC SC IN
7/1/2021 1:37:00 PM
sitecore\jerry.wells
7/1/2021 1:13:09 PM
sitecore\jerry.wells
/sitecore/content/JssPublic/Home/Business/Products/SmartSaver/Custom Incentives/Content/NEW/Tab calculators IN DEC DEP/Performance/Multi Column performance/Related Links NC SC IN
/sitecore/content/JssPublic/Home/Partner With Us/Trade Allies/Commercial/Collateral Toolkit/Content/MCNoCache Marketing Collateral/RL Marketing Collateral
Related Documents
2/6/2017 3:25:26 PM
NAM\JWells4
2/2/2017 11:46:49 AM
NAM\JWells4
/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Nuclear Power/Content/Related Documents
Related Links
7/15/2016 5:21:28 PM
NAM\SHaberm
4/5/2016 8:40:07 AM
nam\c81423
/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Energy From Coal/Content/Related Links
Related Links
7/15/2016 5:21:38 PM
NAM\SHaberm
4/5/2016 8:40:07 AM
nam\c81423
/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Pumped Storage Hydro Plants/Content/Related Links
Related Links
7/15/2016 5:21:48 PM
NAM\SHaberm
4/5/2016 8:40:07 AM
nam\c81423
/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Conventional Hydro Plants/Content/Related Links
Related Links
12/1/2016 8:52:32 AM
nam\jholly1
10/26/2016 11:30:40 AM
NAM\LAFrink
/sitecore/content/JssPublic/Home/Energy Education/How Energy Works/Delivering Electricity/Content/Related Links
Related Links NC SC IN KY
10/8/2020 8:55:03 PM
sitecore\janet.kosoglov
9/25/2020 8:42:50 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Energy Education/Energy Centers and Programs/Energy Revolution Energy Efficiency in Schools/energy efficiency kit/Content/Multi Column NC SC KY IN/Related Links NC SC IN KY
Related Links Right Sidebar
6/7/2018 10:34:34 AM
NAM\c81423
3/24/2016 10:18:53 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Content/Related Links Right Sidebar
External Related Links
7/15/2016 5:33:35 PM
NAM\SHaberm
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/External Related Links
Internal Related Links
7/15/2016 5:33:35 PM
NAM\SHaberm
4/7/2016 9:37:24 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/Internal Related Links
Related Links
11/16/2019 2:22:37 PM
sitecore\admin
3/24/2016 1:54:34 PM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Industry Initiatives/Content/Accordion Industry Collab/Related Links
Copy of External Related Links
7/15/2016 5:33:38 PM
NAM\SHaberm
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/Copy of External Related Links
External Related Links
7/15/2016 5:33:39 PM
NAM\SHaberm
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/External Related Links
Internal Related Links
7/15/2016 5:33:47 PM
NAM\SHaberm
4/7/2016 9:37:24 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Choosing the Right PEV/Content/Internal Related Links
External Related Links
7/15/2016 5:33:53 PM
NAM\SHaberm
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Charging Your PEV/Content/External Related Links
Internal Related Links
7/15/2016 5:33:53 PM
NAM\SHaberm
4/7/2016 9:37:24 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Charging Your PEV/Content/Internal Related Links
External Related Links
7/15/2016 5:33:55 PM
NAM\SHaberm
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/External Related Links
Internal Related Links
7/15/2016 5:33:55 PM
NAM\SHaberm
4/7/2016 9:37:24 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/Internal Related Links
Related Links
11/18/2016 1:06:17 PM
NAM\KMcCart
11/18/2016 1:05:23 PM
NAM\KMcCart
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/Installing a Charging Station/Content/Related Links
External Related Links
6/7/2018 10:03:41 AM
NAM\c81423
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/History of EVs/Content/Multi Column History of PEVs/External Related Links
Internal Related Links
6/7/2018 10:03:45 AM
NAM\c81423
4/7/2016 9:37:24 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/History of EVs/Content/Multi Column History of PEVs/Internal Related Links
External Related Links
6/7/2018 11:37:58 AM
NAM\c81423
3/24/2016 2:54:16 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/FAQ/Content/Multi Column/External Related Links
Internal Related Links
6/7/2018 11:37:44 AM
NAM\c81423
4/7/2016 9:37:24 AM
NAM\tmitch3
/sitecore/content/JssPublic/Home/Energy Education/Energy Savings And Efficiency/Plug In Electric Vehicles/FAQ/Content/Multi Column/Internal Related Links
Related Links 1
2/10/2022 9:56:48 PM
sitecore\janet.kosoglov
2/8/2022 10:45:16 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Funding Guidelines/Content/Related Links 1
Related Links
4/3/2017 11:24:47 AM
NAM\RBGilbe
3/21/2017 2:30:44 PM
NAM\RBGilbe
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Community Initiatives/Charlotte Community Affairs/Content/Related Links
Copy of Related Links
11/16/2021 12:46:25 AM
sitecore\janet.kosoglov
6/30/2020 8:38:46 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Content/Multi Column New/Copy of Related Links
Related Links
3/29/2021 12:09:51 AM
sitecore\janet.kosoglov
6/30/2020 8:38:46 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Hometown OLD/Content/Multi Column NoCache/Related Links
Related Links
2/16/2022 7:14:41 PM
sitecore\janet.kosoglov
2/16/2022 7:12:51 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/North Carolina/Hometown/Content/Multi Column/Related Links
Copy of Related Links
11/16/2021 12:48:08 AM
sitecore\janet.kosoglov
6/30/2020 8:38:46 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/South Carolina/Content/Multi Column New/Copy of Related Links
Related Links
3/3/2022 2:35:42 PM
sitecore\janet.kosoglov
2/25/2022 4:06:00 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/SC Storm Resiliency/Content/Related Links
Copy of Related Links
11/16/2021 12:44:06 AM
sitecore\janet.kosoglov
5/3/2019 12:26:29 PM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Florida/Content/Multi Column New/Copy of Related Links
Related Links
11/16/2021 12:42:40 AM
sitecore\janet.kosoglov
1/20/2020 6:12:18 PM
sitecore\Janet.Kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Ohio/Content/Multi Column New/Related Links
Copy of Related Links
11/17/2021 3:38:25 PM
sitecore\janet.kosoglov
11/17/2021 3:38:21 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Kentucky/Content/Multi Column New/Copy of Related Links
Copy of Related Links
11/17/2021 3:37:36 PM
sitecore\janet.kosoglov
11/17/2021 3:37:35 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Indiana/Content/Multi Column New/Copy of Related Links
Related Links
1/11/2019 1:44:34 PM
NAM\c81423
1/3/2019 9:38:31 AM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Online Application/Content/Related Links
RelatedLinks CountyList SHARED
10/11/2018 12:51:17 PM
NAM\c81423
10/5/2018 11:48:36 AM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/K 12 Education/Content/RelatedLinks CountyList SHARED
Related Links
5/22/2019 10:50:41 AM
NAM\c81423
5/3/2019 11:54:08 AM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/K 12 Education/Content/Multi Column New/Related Links
Related Links SHARED
5/22/2019 10:51:11 AM
NAM\c81423
5/3/2019 11:43:09 AM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Nature/Content/Multi Column New/Related Links SHARED
Related Links
5/22/2019 10:51:34 AM
NAM\c81423
5/3/2019 12:15:56 PM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Workforce Development/Content/Multi Column New/Related Links
Related Links
5/22/2019 10:52:06 AM
NAM\c81423
5/3/2019 12:16:40 PM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Duke Energy Foundation/Local Impact/Content/Multi Column New/Related Links
/sitecore/content/JssPublic/Home/Community/Lakes/Drought Management Advisory/Yadkin Pee Dee DMAG/Content/Related Links
Related Links
2/17/2017 2:36:23 PM
nam\c81423
2/16/2017 1:45:48 PM
nam\c81423
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Content/Multi Column/Related Links
Related Links Micro
4/30/2021 5:47:47 PM
sitecore\jerry.wells
4/25/2016 9:30:17 AM
nam\jholly1
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Distribution Lines Vegetation Management/Content/Distribution Lines MultiColumn/Related Links Micro
Related Links Micro
7/15/2016 6:05:48 PM
NAM\SHaberm
4/25/2016 9:42:13 AM
nam\jholly1
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Transmission Lines Vegetation Management/Content/Related Links Micro
Related Links IN
11/16/2019 2:55:52 PM
sitecore\admin
4/1/2019 4:24:38 PM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links IN
Related Links KY
11/16/2019 2:55:52 PM
sitecore\admin
4/3/2019 10:10:47 AM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links KY
Related Links OH
11/16/2019 2:55:52 PM
sitecore\admin
4/3/2019 10:12:59 AM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links OH
Related Links Micro NC SC FL
11/16/2019 2:55:53 PM
sitecore\admin
4/1/2019 4:15:11 PM
NAM\c81423
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/How We Manage Trees/Right Tree Right Place/Content/Related Links Micro NC SC FL
Related Links Micro ALL
9/10/2021 7:04:16 PM
sitecore\janet.kosoglov
9/10/2021 7:04:12 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/How We Manage Rights of Way/Content/Related Links Micro ALL
Related Links Micro
7/15/2016 6:07:24 PM
NAM\SHaberm
4/25/2016 1:18:52 PM
nam\jholly1
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Distribution Lines Guidelines and Restrictions/Content/Related Links Micro
Related Links Micro CAR
2/11/2022 6:54:40 PM
sitecore\janet.kosoglov
10/5/2021 1:56:42 AM
sitecore\van.parker
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro CAR
Related Links Micro FL
2/11/2022 6:55:10 PM
sitecore\janet.kosoglov
2/11/2022 6:55:09 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro FL
Related Links Micro MW
2/11/2022 6:55:35 PM
sitecore\janet.kosoglov
2/11/2022 6:55:34 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What can you do in Right of Way/Transmission Lines Guidelines/Content/Related Links Micro MW
Related Links Micro
7/15/2016 6:07:40 PM
NAM\SHaberm
4/25/2016 1:14:59 PM
nam\jholly1
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/What is a Right of Way/Content/Related Links Micro
Related Links
2/8/2019 12:28:03 PM
nam\rvp0696
1/21/2019 9:46:03 AM
nam\rvp0696
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Emerald Ash Borer/Content/Intro Text Container/Related Links
NEW Related Links
5/14/2021 1:13:13 PM
sitecore\jerry.wells
9/6/2016 11:40:10 AM
NAM\EYoung2
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/What is a Pipeline/Content/Multi Column/NEW Related Links
Related Links
5/14/2021 1:14:51 PM
sitecore\jerry.wells
8/31/2016 10:32:06 AM
NAM\EYoung2
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Communicating with Prop Own/Content/Multi Column/Related Links
Related Links
5/14/2021 1:19:46 PM
sitecore\jerry.wells
8/31/2016 10:32:06 AM
NAM\EYoung2
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Before you Plant or Build/Content/Multi Column/Related Links
Related Links
5/14/2021 1:22:12 PM
sitecore\jerry.wells
8/31/2016 10:32:06 AM
NAM\EYoung2
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Identifying Pipelines/Content/Multi Column/Related Links
Related Links
5/14/2021 1:23:51 PM
sitecore\jerry.wells
8/31/2016 10:32:06 AM
NAM\EYoung2
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Pipeline Construction/Content/Multi Column/Related Links
Related Links
9/12/2016 12:33:51 PM
nam\tmitch3
8/31/2016 10:32:06 AM
NAM\EYoung2
/sitecore/content/JssPublic/Home/Community/Trees and Rights of Way/Ohio Kentucky Gas Pipelines/Pipeline Clearance FAQ/Content/Related Links
Right Storm Related Links
11/16/2019 2:23:13 PM
sitecore\admin
10/7/2016 12:13:04 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Customer Service/Storms TESTING ONLY/Content/Right Storm Related Links
Right Storm Related Links
9/11/2017 7:08:44 PM
nam\rvp0696
10/7/2016 12:13:04 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Outages/Severe Weather/Content/Right Storm Related Links
Related Links right sidebar
11/3/2016 8:24:33 AM
NAM\CBIcenh
10/31/2016 9:55:49 AM
NAM\CBIcenh
/sitecore/content/JssPublic/Home/Safety and Preparedness/Contractors First Responders/Content/Multi Column/Related Links right sidebar
Related Links
10/2/2019 9:42:30 AM
nam\rvp0696
9/26/2019 8:46:25 AM
nam\rvp0696
/sitecore/content/JssPublic/Home/Safety and Preparedness/Overhead Power Lines/Content/Multi Column/Related Links
Related Links
7/15/2016 5:39:38 PM
NAM\SHaberm
5/5/2016 1:59:45 PM
nam\tmitch3
/sitecore/content/JssPublic/Home/Safety and Preparedness/Electric Safety/Content/Related Links
Related Links
12/21/2017 3:40:18 PM
NAM\CBIcenh
10/26/2017 12:54:07 PM
NAM\CBIcenh
/sitecore/content/JssPublic/Home/Spanish/Servicios al Hogar/StrikeStop/Content/Multi Column Strikestop/Related Links
Related Links
9/7/2016 7:40:59 PM
nam\tmitch3
9/6/2016 12:33:51 AM
nam\tmitch3
/sitecore/content/JssPublic/Home/Spanish/Facturas y pagos/Pago Automatico/Content/Related Links
Related Links
11/16/2019 2:23:47 PM
sitecore\admin
6/6/2016 4:53:44 PM
nam\jholly1
/sitecore/content/JssPublic/Home/Spanish/Facturas y pagos/Facturacion Presupuestada/Content/Related Links
Related Links
8/13/2020 8:51:08 PM
sitecore\van.parker
5/21/2020 8:34:53 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Payment Options/Multi Column/Related Links
Related Links all but FL
6/29/2021 4:06:36 PM
sitecore\jerry.wells
5/14/2020 8:45:05 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Energy Saving Tips/Multi Column/Related Links all but FL
Related Links FL
6/29/2021 5:24:21 PM
sitecore\jerry.wells
5/14/2020 8:45:05 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/DEUpdates/Business Resources/Content/Push Down Panel/Energy Saving Tips/Multi Column/Related Links FL
Related Links DEC
6/16/2021 3:18:18 AM
sitecore\william.natta
3/4/2020 6:24:39 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEC
Related Links DEF
6/16/2021 4:05:43 AM
sitecore\william.natta
3/4/2020 6:24:39 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEF
Related Links DEI
6/16/2021 4:00:32 AM
sitecore\william.natta
3/4/2020 6:24:39 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEI
Related Links DEK
6/16/2021 3:58:08 AM
sitecore\william.natta
3/4/2020 6:24:39 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEK
Related Links DEO
6/16/2021 3:55:00 AM
sitecore\william.natta
3/4/2020 6:24:39 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEO
Related Links DEP
6/16/2021 3:49:44 AM
sitecore\william.natta
3/4/2020 6:24:39 PM
sitecore\janet.kosoglov
/sitecore/content/JssPublic/Home/info/Agency Resources/Content/Multi Column Old R4 hidden/Related Links DEP
Right col helpful resources
1/19/2022 11:21:42 PM
sitecore\van.parker
12/22/2021 7:08:09 PM
sitecore\van.parker
/sitecore/content/JssPublic/Home/info/ChoiceSupplierInfo/Content/Intro copy/Right col helpful resources
Related Links
9/16/2016 2:53:46 PM
NAM\JWells4
9/16/2016 2:53:41 PM
NAM\JWells4
/sitecore/content/JssPublic/Home/info/duke energy logo request/Content/Related Links
Copy of Related Links
11/9/2016 11:28:34 AM
nam\jholly1
11/9/2016 11:28:29 AM
nam\jholly1
/sitecore/content/JssPublic/Home/info/duke energy logo request/logo terms of use/Content/Copy of Related Links
Check that the description for each term is contained in one or more dd elements.
Check that the one or more dd elements immediately follow the one or more dt elements containing the term being described.
<dl title="Nautical terms">
<dt>Knot</dt>
<dd>
<p>A <em>knot</em> is a unit of speed equaling 1
nautical mile per hour (1.15 miles per hour or 1.852
kilometers per hour).</p>
</dd>
<dt>Port</dt>
<dd>
<p><em>Port</em> is the nautical term (used on
boats and ships) that refers to the left side
of a ship, as perceived by a person facing towards
the bow (the front of the vessel).</p>
</dd>
<dt>Starboard</dt>
<dd>
<p><em>Starboard</em> is the nautical term (used
on boats and ships) that refers to the right
side of a vessel, as perceived by a person
facing towards the bow (the front of the vessel).</p>
</dd>
</dl>
The purpose of this document is to create a discussion from which we may create a kind of Working Agreement around Code Review on Ninja Turtles. It sets out to do so by addressing the following:
The current state of our code review process
Suggestions about moving forward
How to improve at Code Reviews and PRs in general
State of the Code Review
The way that Ninja Turtles does Code Review is pretty much exclusively via Pull Requests; if you are a developer on Ninja Turtles, this is where most of your feedback will come from. At present, the most experienced of our team does most of the Code Review, and indeed it sometimes seems as though every PR is reviewed by 1 or 2 people, one of whom approves the PR before it is merged. Put simply, it's sort of the exemplification of the 80/20 rule in action. For the purposes of this conversation, I will refer to the ones doing most of the reviewing as "the 20%" (following the adage that 80% of the work is done by 20% of the team) and the devs that are doing less of the reviewing as "the 80%". Obviously this is not exact, but you get the idea.
The goal should be to get all developers to the point where they're contributing (and receiving) a more equitable share of Code Review, rather than the current 80/20 divide.
Why Improve Code Review?
As noted above, Ninja Turtles is a team that benefits from a diversity of experience levels, but the natural consequence of this is that the developers with the most knowledge and experience with code review (the 20%) end up doing most of the code review. This stands to reason, and is actually quite normal, and probably beneficial. But it can be taxing for those devs doing the lion's share of the reviewing. It is a knife that cuts both ways; while those experienced devs may begin to feel resentment that the responsibility rests on them, the other devs feel inadequate, that they're not pulling their weight, or that they aren't given a fair chance to review before the stuff in their wheelhouse is "taken". Thus, everyone gets further entrenched in their respective roles. This is not great, but it's a problem that won't solve itself. It will require action as a team (more on this later).
But beyond that, there are several compelling reasons for us to improve on our Code Reviews.
From a development standpoint, Code Review is the least expensive way to catch mistakes.
Code Review provides opportunities to spread domain knowledge across the team, thereby preventing a complete reliance on trickle-down economics, er, knowledge.
Code Reviews can provide opportunities to learn, mentor, and teach.
Code Review builds a stronger foundation of trust or something
"Effectiveness of code review for determining faults in software is between 30% and 35% more effective than standard unit testing"
Steve McConnell, Code Complete
So, we understand the value of Code Review and we see a need to improve, but exactly how do we improve them?
Going Forward
I did a lot of reading and consulted some mentors to arrive at some of these conclusions. I will provide the resources I consulted below; I found them to be quite useful, and I encourage all who are interested to review them. After this, I propose a few changes. I will lay them out here and my hope is we can have a conversation about which changes we would like to adopt going forward and thus create something of a "Working Agreement" surrounding Code Reviews.
Doing so obviously won't cause a miraculous change overnight, but I think implementing some of these suggestions is a good first step toward moving away from the 80/20 divide described above and getting everyone on the same level.
General suggestions:
With two approvals now required to on a PR, perhaps it would make sense to rotate a more senior reviewer with a less senior reviewer or two (this would fall inline with guidelines I've seen) and let them tackle it independently. Ideally, they are both able to approve or provide feedback to the dev until they are ready to approve, but if not, after a certain period of time, the devs could come together and talk about why they are not feeling comfortable approving the PR.
Maybe we should define our style guide a little bit more clearly (currently, it has exactly one entry ) and we should definitely set up linting to enforce some of the simple things, such as consistent formatting. Devs should not be pushing up code that has completely preventable errors and reviewers should not be wasting precious cognitive energy on simple errors that can be caught by computers
Consider limiting how much code you will look at, at least in one sitting; looking at code you didn't write for a long time can be exhausting. You might consider timeboxing your reviews to one hour and come back later if there's more you feel that needs to be done, or you may set a line count of around 200-400 lines before taking a break and doing something else for awhile.
Things to think about and maybe discuss:
Expecting someone to get better at code reviews by simply looking at someone else's code just isn't a very good or efficient methodology. (See Jim Bird's article below). What can we do bridge that gap in a more proactive way? Perhaps some kind of structured "pair reviewing"?
I touched on this above but another thing that Jim Bird suggests is keeping the number of reviewers small. In the long run, I see the value in having all the devs swarm it and knocking out a PR, but in the shorter term, it might make sense to slow down and focus on the quality over quantity and getting all devs up to around the same contribution level?
For the 20% currently doing 80% of the reviewing:
Consider alternating PRs. Maybe everyone doesn't need to look at every PR, but a few people look at different ones?
Not sure what this would look like, but I'm sure it's doable.
Step back. Don't be the first to review. Give some space to let some of the other devs (the 80%) step up. This will hopefully take a bit of the pressure off of you and lets the 80% leave the kind of feedback that may be easier for them to recognize as they level up their Code Review game.
This may mean that we add a Bitbucket integration to Slack or Teams (if that's available) or when a new PR is submitted, the submitter alerts the team
Be specific when answering questions. Answering a question about how or why something works with a block of code will solve the problem, but it doesn't help the asker understand the issue any better. A link to an article or MDN can be sufficient.
For the 80%:
Have an algorithm/checklist for things you want to review. Below you'll find an overview of the kinds of things to look for.
Step up.
If we implement the changes suggested above, you will have some room to make some suggestions early on. But having the 20% step back only works if the 80% step up.
Ask questions. Even in PRs. Questions like "Why are we doing it like this?" or "I'm having trouble understanding this block of code, can you explain it to me?" are totally valid within a PR. Doing it in the PR creates a paper trail of the conversation. If you've reviewed the code and you don't have feedback but you also don't feel you can approve the PR, it might be an indication that you need to ask more questions.
Ask to pair with somebody more experienced at reviewing code. You can get a feel for their process and ask questions about how they approach Code Reviews and spot things.
Don't underestimate the value of the input of someone with less context. Having anyone that isn't you review your code is helpful, because we all get myopic about our own code and can miss things that are really obvious to a fresh perspective.
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:
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:
Welcome to Duke Energy Enterprise JIRA ~ Please enter a ticket through MyIT if you have any significant problems with the tool. Thank you! Have a great day!
The check mark that indicates a completed step in the form is an <svg> image without a text alternative. Blind and low vision users will not know that this is a completed step.
Recommendation
Use the SVG <title> element to provide a text alternative that's appropriate for the context. To ensure support in assistive technologies, provide role="img" and aria-labelledby attributes in the <svg> element.
SVG with <title> element, role="img", and the aria-labelledby attribute (Simplified code)
LAN IDs, Employee IDs, and Responsibility Codes do not currently show in modern search on a person's profile card. Please use our specialized People Search Page or access the information in Delve.
The Portal is your default home page when you launch the Internet on your Duke Energy workstation. You can also access the Portal from your personal laptop or mobile device.
The Portal is a robust communication tool designed to be informative and engaging. On the home page, top company stories and employee features are posted frequently. The home page also contains media articles, company events, and personnel announcements. You are encouraged to add comments to articles and submit company-sponsored events and accomplishments.
The Portal also provides access to key transactional items such as company facts and branding, HR information (e.g., benefits and pay), policies, services (e.g., mail service and expense management), departments, operations, and other tools and applications.
Portal Site Managers
Brand, Creative & Digital
Contact & Personal Information
Contingent Worker Actions
COVID-19 Employee Resources
COVID-19 Manager Resources
Customer Experience & Services
PORTAL GUIDE
Manage Your Information
Delve profile pages exist for everyone with a LAN ID who is an employee of or contingent worker for Duke Energy.
Where did my profile information come from?
The organizational and location information in your Delve Portal profile comes from Active Directory.
This section of your Delve Profile is optional. You can choose to enter additional information about yourself, including:
About me
Home phone
Projects
Skills and expertise
Schools and education
Interests and hobbies
Filling in this information will help others find you when they're looking for someone with knowledge or experience about your areas of expertise, or it can help you connect with others who share your interests.
To edit this information, go to your Delve profile and click the Update Profile button.
Manage Your Photo in Workday
Manage Your Photo in O365
Instructions for O365 Photos
When you go to Delve, click Me in the left navigation. Use the camera icon over your photo to change your picture. This will change it throughout the O365 environment, including Outlook, Teams, and SharePoint. Changing your photo here will not push it to Workday. Work is underway to enable the Workforce Photo Tool to integrate with O365.
Photo Guidelines
Your photo must be business-appropriate. That would be:
A forward-facing, head-and-shoulders shot of you without sunglasses, ball cap, kids or family dog serves as a good starting point. Consider the limited space that will display your face when you select a photo. The goal is to see what you look like!
Please follow copyright rules when selecting a photo to update. You must own rights to any image you post.
PORTAL GUIDE
Accessing the Portal Remotely
You can access the Portal from any computer or mobile device. You do not need to be on the Duke Energy network, and you do not need to use a Duke Energy computer or device that is in the Personal Mobile Device program.
From the SharePoint App
You can download the SharePoint app from an app store. The home icon in the app takes you to the Portal homepage.
From a Mobile Browser
Open your web browser (i.e., Edge, Chrome or a similar Web browser)
If you are not on the network or on a device managed through the Personal Mobile Device program, you will need to log into your Office 365 account.
Enter your LAN ID and password.
Follow the steps required for your multi-factored authentication (MFA). This is most likely a phone call to the number you entered when you created your Office 365 profile. For questions, .
Keep in mind that some Duke Energy applications, are not available when you are off the network, and you will experience errors if you click links to them.
PORTAL GUIDE
Intranet Design Awards
The top ten intranets in the world, published annually by the Nielsen Norman Group. We purchase and publish these reports so that we can get a glimpse behind firewalls into the best practices with other companies. Duke Energy is a two-time winner.
PORTAL GUIDE
The Modern Portal Project Is Complete! »
Site Manager Guide»
Welcome to Support Chat. Please enter a question and a chat technician will soon be with you. Just type in a question, keyword or phrase below and I’ll take you to the information you’re looking for.
Bryan1:59 PM
Hi, I have a macbook air... is it at all possible for me to find a workaround that allows for me to run two external monitors in addition to the builtin one?
System1:59 PM
The following associated data has been added:
Customer Information
Incident: INC000032293420
System1:59 PM
The following associated data has been modified:
Customer Information
Incident: INC000032293420
System1:59 PM
System Message: Jamal Clair is online and ready to chat.
Jamal Clair1:59 PM
Hello my name is Jamal. Can you please provide your employee id, contact number, Computer serial number, and current location please?
System2:02 PM
System Message: undefined has rejoined the session
2:03 PM
Contact number: 551-254-5505... Serial Number is: FVFGX02PQ6L8 and my current location is NY,NY. I am not sure what my employee number is or how to access it but I am going to try to look into it.
2:04 PM
Could it be this: CW-Professional 47146?
Jamal Clair2:04 PM
Thank you
Jamal Clair2:04 PM
Do you have a dockingstation
Jamal Clair2:04 PM
docking station
2:08 PM
Yes two actually, but unfortunately the macbook air was the m1 chip was the one model apple made that doesn't nativley support multiple screens. This is one of my first weeks, I think my manager said it was an accident and that I was supposed to be sent a macbook pro but I have no idea if that is still in the works. Various sources on the internet claim to have found workarounds but so far I have not been able to successfully replicate these 'hacks'. If the firewall allowed me to cast via airplay then I think I could connect to my external displays wirelessly. As I speak this computer is driving three large external screens but all three are a mirror of each other with the only distinction being the builtin display which is not mirroring the other three screens.
Jamal Clair2:09 PM
is this your personal device
2:09 PM
no it is my duke device
2:10 PM
My personal computer is a pc which has no issue with this
Jamal Clair2:10 PM
so you want to be able to control multiple screens with airplay
2:10 PM
As a last resort
Jamal Clair2:10 PM
but duke firewall blocks that
2:11 PM
ideally I would be able to drive two or three external screens in the (extend... (as opposed to mirror))-display mode
2:13 PM
I don't have much expertice but I figured someone else may have had this issue and found some other solution... like for instance an external gpu that can drive the extra screens ? idk tbh but prior to this job I got very comfortable with a three screen workflow and it isn't the end of the world but going back down to two or one screens is a pretty significant inconvenience considering one is usually dedicated to a microsoft teams call / screen share
2:15 PM
I feel like I may be blowing this out of proportion... this is just a matter of preference that I have grown accustomed to and is not actually a necessity. I have a creeping fear that it may just not be possible with this computer.
\
BitLocker keys for LAPTOP-9LGJ3JGS
OPERATING SYSTEM DRIVEKey ID:\ f035f7c1-b011-4a97-ad86-a1e2b994037d\ Recovery key:\ 277827-520718-692857-678557-601997-238381-127116-121748
Jokes aside the items in this list are concepts that I usually see beginners struggling with. At the same time, learning these concepts will vastyly improve your testing game. I know it did with mine.
1. Everything is a DOM node
This is usually the first misconception that beginners have when they start approaching Testing Library. It is especially true for those developers like me that came from Enzyme.
Many think that getByText and other helper methods return some special wrapper around the React component. People even ask how to implement a getByReactComponent helper.
When you work with Testing Library you are dealing with DOM nodes. This is made clear by the first Guiding Principle:
If it relates to rendering components, then it should deal with DOM nodes rather than component instances, and it should not encourage dealing with component instances.
If you want to check for yourself, it's as simple as this:
Once you realize that you're dealing with DOM nodes you can start taking advantage of all the DOM APIs like querySelector or closest👍
2. debug's optional parameter
Since we now know that we're dealing with a DOM structure, it would be helpful to be able to "see" it. This is what debug is meant for:
ometimes thought debug's output can be very long and difficult to navigate. In those cases, you might want to isolate a subtree of your whole structure. You can do this easily by passing a node to debug:
3. Restrict your queries with within
Imagine you're testing a component that renders this structure:
You want to test that each ID gets its correct value. You can't use getByText('Apples') because there are two nodes with that value. Even if that wasn't the case you have no guarantee that the text is in the correct row.
What you want to do is to run getByText only inside the row you're considering at the moment. This is exactly what within is for:
4. Queries accept functions too
You have probably seen an error like this one:
Usually, it happens because your HTML looks like this:
The solution is contained inside the error message: "[...] you can provide a function for your text matcher [...]".
What's that all about? It turns out matchers accept strings, regular expressions or functions.
The function gets called for each node you're rendering. It receives two arguments: the node's content and the node itself. All you have to do is to return true or false depending on if the node is the one you want.
An example will clarify it:
We're ignoring the content argument because in this case, it will either be "Hello", "world" or an empty string.
What we are checking instead is that the current node has the right textContent. hasText is a little helper function to do that. I declared it to keep things clean.
That's not all though. Our div is not the only node with the text we're looking for. For example, body in this case has the same text. To avoid returning more nodes than needed we are making sure that none of the children has the same text as its parent. In this way we're making sure that the node we're returning is the smallest—in other words the one closes to the bottom of our DOM tree.
5. You can simulate browsers events with user-event
Ok, this one is a shameless plug since I'm the author of user-event. Still, people—myself included—find it useful. Maybe you will too.
All user-event tries to do is to simulate the events a real user would do while interacting with your application. What does it mean? Imagine you have an input field, and in your tests, you want to enter some text in it. You would probably do something like this:
It works but it doesn't simulate what happens in the browser. A real user would most likely move the mouse to select the input field and then start typing one character at the time. This, in turns, fires many events (blur, focus, mouseEnter, keyDown, keyUp...). user-event simulates all those events for you:
Unable to find an element with the text: Hello world.
This could be because the text is broken up by multiple elements.
In this case, you can provide a function for your text
matcher to make your matcher more flexible.
<div>Hello <span>world</span></div>
importReactfrom"react";import
importReactfrom"react";import
const{debug}=render(<MyComponent
const{debug}=render(<MyComponent
<table><thead><tr
importReactfrom"react";import
import{render,screen,within}
fireEvent.change(input,{target:{
importuserEventfrom"@testing-library/user-event";
0
4
typesc
1
381
20
361
test
1
262
14
248
DefinitionOfDone
Component Creation - Definition of Done
Table of Contents:
Intro
Technical Overview
Practical Overview
Definition of Done
Sitecore
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.
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!
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.
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.
Basic Instructions for Creating a Component
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.
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.
Installing, Configuring, and Uninstalling Citrix Workspace on Chrome OS Devices
Contents
****
Installing Citrix Workspace on Chrome OS Devices ............................................................. 2
Configuring Citrix Workspace on Chrome OS Devices.......................................................... 3
Secondary configuration of Citrix Workspace on Chrome OS Devices…………………. 6
Uninstalling Citrix Workspace on Chrome OS Devices ......................................................... 9
\
Installing Citrix Workspace on Chrome OS Devices
1. On the Chrome OS device, open the Chrome Web Store and search for ‘Citrix Workspace’. Select ‘Citrix Workspace for Chrome OS’. Click Add to Chrome and Add App When prompted.
****
Configuring Citrix Workspace on Chrome OS Devices
****
1. Upon installation, click the Launcher Icon and then click the Citrix Workspace icon. You may need to click on All Apps to see the Citrix Workspace icon.
****
2. You will be prompted to enter a server address: Enter
****
3. Next, enter your Username, Password and your Passcode when prompted. Click Log On once you’ve finished entering your information.
NOTE:
• When using a physical key fob, your Passcode is your unique PIN + RSA token ID.
• When using a soft token, the PIN is not included as part of the Passcode.
4. The available Citrix applications and desktops should now appear as icons. You have successfully installed the Citrix Workspace software on your computer.
5. To launch a virtual desktop, select the Desktops tab at the bottom of the screen and select the assigned desktop. And to launch a virtual application, select the Apps tab at the bottom of the screen and select the application.
****
****
****
****
****
****
****
****
****
****
****
****
Secondary configuration of Citrix Workspace on Chrome OS Devices
****
****
NOTE: If you have not set up an authentication method in O365, by using this job aid , please do so BEFORE adding the Multifactor Authentication Citrix Portal URL
****
Be sure you set up a primary authentication method and a secondary authentication method.
****
o Choose your preferred primary authentication method from the drop-down box under “what’s your preferred option?”
****
1. Upon installation, click the Launcher Icon and then click the Citrix Workspace icon. You may need to click on All Apps to see the Citrix Workspace icon.
2. You will be prompted to enter a server address: Enter in order to setup the Citrix Portal that utilizes Multifactor Authentication. Afterwards, select Connect.
3. Next, enter your Username and Password when prompted. Click Log On once you’ve finished entering your information.
****
NOTE: You will be prompted to authenticate via the method that you setup within your Office 365 Authentication Profile:
· Via a call to a phone number you provided during setup (e.g., mobile phone number, secondary mobile phone number, or home phone number where you will be working)
· The Microsoft Authenticator application
****
****
4. To access your assigned virtual desktops or virtual applications, click either the DESKTOPS or APPS buttons at the top of the page.
****
Uninstalling Citrix Workspace on Chrome OS Devices
1. Open the Launcher (Magnifying glass) on your taskbar. Click the upward arrow to show all installed apps and locate the Citrix Workspace icon.
2. Right-click the Citrix Workspace Icon. If you are not using a mouse, then press the Alt key when you click on the Citrix Workspace icon to bring up the ‘right click’ menu options
3. Click Uninstall. Click Remove when asked to confirm removal.
****
Setup
This section outlines getting the application (for the JSS public site) setup for development.
Get the repo
cd to where you keep your repos
TechnicalOverview
Component Creation - Technical Overview
Table of Contents:
Intro
👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ 👨⚕ Intro
Component Creation - Intro
Table of Contents:
Intro
🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 🧑🏫 PracticalOverview
Component Creation - Practical Overview
Table of Contents:
Intro
Forms
Dynamically generating forms with React and Sitecore JSS
Creating the FormField
Navigate to page http: //local.duke-energy.com:3000/home/products/outdoor-lighting/contact
<!--
Hero Section wrapper
- Component below hero is required to use a white background color
-->
<section class="relative 2xl:px-24 2xl:pt-24">
<!--
Hero Container
-->
<div class="relative container-5xl flex flex-col justify-center aspect-16/12 sm:aspect-16/9 md:aspect-16/7 lg:aspect-16/6 xl:aspect-16/5 px-24 sm:px-32 md:px-48 py-64 md:py-48">
<!--
- Leave alt attribute empty
- Use 'srcset' and 'sizes' attributes to optimize the hero image
-->
<img
class="absolute top-0 left-0 object-cover object-right w-full h-full 2xl:rounded-lg bg-gray-light"
src="image.jpg?w=800&as=1&bc=ffffff"
srcset="image.jpg?w=800&as=1&bc=ffffff 800w,
image.jpg?w=1600&as=1&bc=ffffff 1600w"
sizes="(min-width: 768px) 1600px, 800px"
alt=""
width="1600"
height="500"
loading="lazy"
>
<!--
Overlay
- Change background gradient colors based on theme of hero
- Default - "from-blue-dark to-blue sm:via-blue"
-->
<div
class="absolute top-0 left-0 h-full w-full 2xl:rounded-l-lg md:w-3/4 bg-gradient-to-tr md:bg-gradient-to-r md:to-transparent opacity-80"
aria-hidden="true"
></div>
<!--
Content Container
-->
<div class="relative w-full container-xs md:container-4xl">
<!--
If dark theme/gradient, text is white
- Use "text-white"
If light theme/gradient, text can be gray or blue
- Use "text-gray-dark" -or- "text-blue"
Default
- "text-white"
-->
<div class="w-full md:w-1/2 text-center md:text-left">
<!-- Hero Title
- If this is the page title, you should use an <h1> element
- Use an <h2> element if you already have a page title on the page
-->
<h2
class="text-3xl md:text-2xl-fixed xl:text-3xl"
id="hero-title"
>
Hero Title
</h2>
<!-- Hero Short Description-->
<p class="text-lg xl:text-xl mt-12 lg:mt-16">
Description goes here.
</p>
<!-- Hero Actions -->
<div class="flex justify-center md:justify-start gap-16 lg:gap-24 mt-16 lg:mt-24">
<!--
If dark theme/gradient
- Use "btn-primary-reversed"
If light theme/gradient
- Use "btn-primary"
Default
- "btn-primary-reversed"
-->
<a
class="btn btn-primary-reversed btn-md"
href="#"
id="hero-primary-action"
aria-labelledby="hero-primary-action hero-title"
>
Primary Action
</a>
<!--
If dark theme/gradient
- Use "btn-secondary-reversed"
If light theme/gradient
- Use "btn-secondary"
Default
- "btn-secondary-reversed"
-->
<a
class="btn btn-secondary-reversed btn-sm lg:btn-md"
href="#"
id="hero-secondary-action"
aria-labelledby="hero-secondary-action hero-title"
>
Secondary Action
</a>
</div>
</div>
</div>
</div>
</section>
// # TextMatch type accenpts function: https://testing-library.com/docs/queries/about/#textmatch
import { renderWithCTX, screen, Matcher } from "src/lib/testWrappers";
import userEvent from "@testing-library/user-event";
import "@testing-library/jest-dom";
import { Accordion, themeMap } from "./index";
import { compositionFunction } from "./composition";
import data from "./data";
import { stripHTMLTags } from "src/lib/helpers";
jest.mock("src/lib/useIntersection");
describe("Accordion", () => {
const props = compositionFunction(data);
it.skip("should render the correct items", () => {
renderWithCTX(<Accordion {...props} />);
for (let i = 0; i < props.items.length - 1; i++) {
const accordionTitle = screen.getByText(
props?.items[0]?.title?.value as string
);
expect(accordionTitle).toBeTruthy();
}
});
it.skip("should render Accordion Component with image", () => {
renderWithCTX(<Accordion {...props} />);
const img = screen.getByRole("img", { name: /facebook/i });
expect(img).toBeInTheDocument();
});
it("should reveal text when user clicks", async () => {
renderWithCTX(<Accordion {...props} />);
const accordionButton = screen.getByRole("button", {
name: props.items[0].title?.value,
});
// # Full Manual Query:
// (This is actually not good - getByText won't respect eg aria- attrs like getByRole so we aren't actually testing anything)
// These are [not exposed to a11y tree](https://testing-library.com/docs/queries/about/#priority)
// 👉
// screen.getByText(
// /proin laoreet mauris vel urna tempor ultricies\. duis rhoncus lorem sed tellus egestas bibendum\. in aliquam mauris est, vel condimentum metus aliquet a\. nunc volutpat tincidunt nisl luctus pretium\. sagittis elit non, vestibulum metus\. mauris maximus vitae magna in mattis\. integer interdum maximus felis sed placerat\. nam lobortis tellus non felis fermentum, vitae venenatis ligula congue\. donec sit amet luctus odio\. magna commodo, sodales convallis ante scelerisque\. etiam ipsum lorem, rhoncus a sapien id, placerat consequat nunc\. curabitur in orci libero\. morbi tincidunt ante vel sem rutrum tempor\. cras ac purus quis urna maximus volutpat\./i
// );
// # We can still do a findByText,
// but we need to check ourselves whether it is accessible:
// 👉
const hiddenText = await screen.findByText((content, element) => {
const hasTextContent =
element?.innerHTML === props?.items[0]?.text?.value;
return hasTextContent;
});
expect(hiddenText).toBeInTheDocument();
expect(hiddenText).toHaveAttribute("aria-hidden", "true");
await userEvent.click(accordionButton);
// # Compare against textContent stripHTMLTags:
// (similar to https://polvara.me/posts/five-things-you-didnt-know-about-testing-library)
// 👉
// const noTag = stripHTMLTags(props?.items?.[0]?.text?.value as string);
// const [revealedText] = await screen.findAllByText(
// // @ts-ignore
// // eslint-disable-next-line
// (content, element) => element?.textContent === noTag
// // ? console.log('🔥', content) || true : false
// );
// expect(revealedText).toBeInTheDocument();
// console.log('😳', noTag);
// 😳 Proin laoreet mauris vel urna tempor ultricies. Duis rhoncus lorem sed tellus egestas bibendum. In aliquam mauris est, vel condimentum metus aliquet a. Nunc volutpat tincidunt nisl luctus pretium. Duis et ligula semper, sagittis elit non, vestibulum metus. Mauris maximus vitae magna in mattis. Integer interdum maximus felis sed placerat. Nam lobortis tellus non felis fermentum, vitae venenatis ligula congue. Donec sit amet luctus odio. Nam convallis justo vitae magna commodo, sodales convallis ante scelerisque. Etiam ipsum lorem, rhoncus a sapien id, placerat consequat nunc. Curabitur in orci libero. Morbi tincidunt ante vel sem rutrum tempor. Cras ac purus quis urna maximus volutpat.
// # Find by innerHTML:
// 👉
const revealedText = await screen.findByText((content, element) => {
const hasTextContent =
element?.innerHTML === props?.items[0]?.text?.value;
return hasTextContent;
});
expect(revealedText).toBeInTheDocument();
expect(revealedText).toHaveAttribute("aria-hidden", "false");
});
// Overall, probably ultimately more durable / less error prone to use a testid and check aria- attrs
it.skip("renders the correct default style", () => {
const { rerender } = renderWithCTX(<Accordion {...props} />);
const button = screen.getByRole("button", {
name: /accordion trigger one/i,
});
expect(button).toHaveClass(themeMap.default.button);
const altThemeProp = { ...props, theme: "footer" } as const;
rerender(<Accordion {...altThemeProp} />);
expect(button).toHaveClass(themeMap?.footer?.button);
});
});
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.
Technical Overview
Practical Overview
Definition of Done
Sitecore
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.
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:
Types vs. interfaces in TypeScript - LogRocket Blog
Excerpt
It is very simple to get started with TypeScript, but sometimes we need to think more about the best use case for us. In this case, types or interfaces?
The idea of having static type-checking in JavaScript is really fantastic and the adoption of TypeScript is growing more every day.
You started to use TypeScript in your project, you created your first type, then you jumped to your first interface, and you got it working. You concluded that TypeScript, in fact, was helping your development and saving you precious time, but you might have made some mistakes and not followed the best practices when you started to work with types and interfaces in TypeScript.
This is the case for a lot of developers, they don't really know the real difference between type aliases and interfaces in TypeScript.
It is very simple to get started with TypeScript, but sometimes we need to think more about the best use case for us. In this case, types or interfaces?
Types and type aliases
Before we jump into the differences between types and interfaces in TypeScript, we need to understand something.
In TypeScript, we have a lot of basic types, such as string, boolean, and number. These are the basic types of TypeScript. You can check the list of all the basic types [here](https: //www.typescriptlang.org/docs/handbook/basic-types.html#table-of-contents). Also, in TypeScript, we have advanced types and in these [advanced types](https: //www.typescriptlang.org/docs/handbook/advanced-types.html), we have something called [type aliases](https: //www.typescriptlang.org/docs/handbook/advanced-types.html#type-aliases). With type aliases, we can create a new name for a type but we don't define a new type.
We use the type keyword to create a new type alias, that's why some people might get confused and think that it's creating a new type when they're only creating a new name for a type. So, when you hear someone talking about the differences between types and interfaces, like in this article, you can assume that this person is talking about type aliases vs interfaces.
We will use the [TypeScript Playground](https: //www.typescriptlang.org/play/index.html#) for code examples. The [TypeScript Playground](https: //www.typescriptlang.org/play/index.html#) allows us to work and test the latest version of TypeScript (or any other version we want to), and we will save time by using this playground rather than creating a new TypeScript project just for examples.
Types vs. interfaces
The difference between types and interfaces in TypeScript used to be more clear, but with the latest versions of TypeScript, they're becoming more similar.
Interfaces are basically a way to describe data shapes, for example, an object.
Type is a definition of a type of data, for example, a union, primitive, intersection, tuple, or any other type.
Declaration merging
One thing that's possible to do with interfaces but are not with types is declaration merging. Declaration merging happens when the TypeScript compiler merges two or more interfaces that share the same name into only one declaration.
Let's imagine that we have two interfaces called Song, with different properties:
interface Song { artistName: string; }; interface Song { songName: string; }; const song: Song = { artistName: "Freddie", songName: "The Chain" };
TypeScript will automatically merge both interfaces declarations into one, so when we use this Song interface, we'll have both properties.
Declaration merging does not work with types. If we try to create two types with the same names, but with different properties, TypeScript would still throw us an error.
Duplicate identifier Song.
Extends and implements
In TypeScript, we can easily extend and implement interfaces. This is not possible with types though.
Interfaces in TypeScript can extend classes, this is a very awesome concept that helps a lot in a more object-oriented way of programming. We can also create classes implementing interfaces.
For example, let's imagine that we have a class called Car and an interface called NewCar, we can easily extend this class using an interface:
class Car { printCar = () => { console.log("this is my car") } }; interface NewCar extends Car { name: string; }; class NewestCar implements NewCar { name: "Car"; constructor(engine:string) { this.name = name } printCar = () => { console.log("this is my car") } };
Intersection
Intersection allows us to combine multiple types into a single one type. To create an intersection type, we have to use the & keyword:
type Name = { name: "string" }; type Age = { age: number }; type Person = Name & Age;
The nice thing here is that we can create a new intersection type combining two interfaces, for example, but not the other way around. We cannot create an interface combining two types, because it doesn't work:
interface Name { name: "string" }; interface Age { age: number }; type Person = Name & Age;
Unions
Union types allow us to create a new type that can have a value of one or a few more types. To create a union type, we have to use the | keyword.
type Man = { name: "string" }; type Woman = { name: "string" }; type Person = Man | Woman;
Similar to intersections, we can create a new union type combining two interfaces, for example, but not the other way around:
interface Man { name: "string" }; interface Woman { name: "string" }; type Person = Man | Woman;
Tuples
[Tuples](https: //www.typescriptlang.org/docs/handbook/basic-types.html#tuple) are a very helpful concept in TypeScript, it brought to us this new data type that includes two sets of values of different data types.
type Reponse = [string, number]
But, in TypeScript, we can only declare tuples using types and not interfaces. There's no way we can declare a tuple in TypeScript using an interface, but you still are able to use a tuple inside an interface, like this:
interface Response { value: [string, number] }
We can see that we can achieve the same result as using types with interfaces. So, here comes the question that a lot of developers might have — should I use a type instead of an interface? If so, when should I use a type?
Let's understand the best use cases for both of them, so you don't need to abandon one for the other.
What should I use?
This question is really tricky, and the answer to it, you might guess, depends on what you're building and what you're working on.
Interfaces are better when you need to define a new object or method of an object. For example, in React applications, when you need to define the props that a specific component is going to receive, it's ideal to use interface over types:
Types are better when you need to create functions, for example. Let's imagine that we have a function that's going to return an object called, type alias is more recommended for this approach:
type Person = { name: string, age: number }; type ReturnPerson = ( person: Person ) => Person; const returnPerson: ReturnPerson = (person) => { return person; };
At the end of the day, to decide if you should use a type alias or an interface, you should carefully think and analyze the situation — what you're working on, the specific code, etc.
Interface work better with objects and method objects, and types are better to work with functions, complex types, etc.
You should not start to use one and delete the other. Instead of doing that, start to refactor slowly, thinking of what makes more sense to that specific situation.
Remember that you can use both together and they will work fine. The idea here is just to clarify the differences between types and interfaces, and the best use cases for both.
Conclusion
In this article, we learned more about the differences between types and interfaces in TypeScript. We learned that type aliases are advanced types in TypeScript, we learned the best use cases for both types and interfaces in TypeScript, and how we can apply both of them in real projects.
Warnings
Typescript Rules:
Special Props Warning
Most props on a JSX element are passed on to the component, however, there are two special props (ref and key) which are used by React, and are thus not forwarded to the component.
For instance, attempting to access this.props.key from a component (i.e., the render function or propTypes) is not defined. If you need to access the same value within the child component, you should pass it as a different prop (ex: <ListItemWrapper key={result.id} id={result.id} />). While this may seem redundant, it's important to separate app logic from reconciling hints.
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
Install dependencies
Use node -v 14.17.4
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
//` for development so that jurisdiction cookies will work properly.
Setup your .hosts file
We need to set the local IP to local.duke-energy.com so we can use cookies set by the .duke-energy.com domain.
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
Start the project for development.
Inside the project, run npm run start:connected on the command line.
After the app successfully starts up, change the URL to https: //local.duke-energy.com:3000/home.
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.
Technical Overview
Practical Overview
Definition of Done
Sitecore
If you'd prefer to get right to the point and skip the overview, there's a handy checklist here. Otherwise, read on for an in-depth explanation of how one could approach a workflow via Storybook.
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.
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.
// 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,
};
Your office phone may automatically pre-fill; HOWEVER, be sure to uncheck this option. Your primary and secondary methods should be a combination of: a mobile phone number, secondary mobile phone number, a home phone number where you will be working or the Authenticator application
The Authenticator application is the preferred primary method of authentication.
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.
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.
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:
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. See the styles section for more info.
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.
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.
Stories.js
The stories.js file creates a Storybook 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 check out our documentation for Storybook..
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.
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.
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.
This page calls the <SingleStepForm /> component in the layout json from Sitecore which have fields that look like this
<SingleStepForm /> gets rendered via its composition file converts this JSS fields object as its props.
<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 looks like this: (note - only a few fields are shown as an example here)
formModel then gets passed into createFormInit() to process all of this data into something more concise and manageable
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() 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
\
Side note: Important info
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
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
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+$/, },
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
validations: Will either be null or an object that contains if it should show on the confirmation screen and the validationPattern if any
<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
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
Inside the FormField
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 /> 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.
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
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.
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).
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.
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.
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.
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.
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!
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.
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:
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.
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):
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.
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.
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.
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:
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
Typescript Interfaces
When we talk about a type in TypeScript, we mean a collection of things that you can do with a variable (or expression). You might be able to read or write a given property, call a function, use the expression as a constructor, or index into the object. Some objects (like Date) in JavaScript can do nearly all of those! In TypeScript, interfaces are the most flexible way of describing types.
You'll see interfaces used to describe existing JavaScript APIs, create shorthand names for commonly-used types, constrain class implementations, describe array types, and more. While they don't generate any code (and thus have no runtime cost!), they are often the key point of contact between any two pieces of TypeScript code, especially when working with existing JavaScript code or built-in JavaScript objects.
The only job of an interface in TypeScript is to describe a type. While class and function deal with implementation, interface helps us keep our programs error-free by providing information about the shape of the data we work with. Because the type information is erased from a TypeScript program during compilation, we can freely add type data using interfaces without worrying about the runtime overhead.
While that sounds like a simple, one-purpose task, interfaces role in describing types becomes manifest in a large variety of ways. Let's look at some of them and how they can be used in TypeScript programs.
Basics
To define an interface in TypeScript, use the interface keyword:
This defines a type, Greetable, that has a member function called greet that takes a string argument. You can use this type in all the usual positions; for example in a parameter type annotation. Here we use that type annotation to get type safety on the g parameter:
When this code compiles, you won't see any mention of Greetable in the JavaScript code. Interfaces are only a compile-time construct and have no effect on the generated code.
Interfaces: TypeScript's Swiss Army Knife
Interfaces get to play a lot of roles in TypeScript code. We'll go into more detail on these after a quick overview.
Describing an Object
Many JavaScript functions take a "settings object". For example, jQuery's $.ajax takes an object that can have up to several dozen members that control its behavior, but you're only likely to pass a few of those in any given instance. TypeScript interfaces allow optional properties to help you use these sorts of objects correctly.
Describing an Indexable Object
JavaScript freely mixes members (foo.x) with indexers (foo['x']), but most programmers use one or the other as a semantic hint about what kind of access is taking place. TypeScript interfaces can be used to represent what the expected type of an indexing operation is.
Ensuring Class Instance Shape
Often, you'll want to make sure that a class you're writing matches some existing surface area. This is how interfaces are used in more traditional OOP languages like C# and Java, and we'll see that TypeScript interfaces behave very similarly when used in this role.
Ensuring the Static Shape of a Class or constructor Object
Interfaces normally describe the shape of an instance of a class, but we can also use them to describe the static shape of the class (including its constructor function). We'll cover this in a later post.
Describing an Object
You can also use interfaces to define the shape of objects that will typically be expressed in an object literal. Here's an example:
Describing Simple Types
Note the use of the ? symbol after some of the names. This marks a member as being optional. This lets callers of createButton supply only the members they care about, while maintaining the constraint that the required parts of the object are present:
You typically won't use optional members when defining interfaces that are going to be implemented by classes.
Here's another example that shows an interesting feature of types in TypeScript:
Note that we didn't annotate pt in any way to indicate that it's of type Point. We don't need to, because type checking in TypeScript is structural: types are considered identical if they have the same surface area. Because pt has at least the same members as Point, it's suitable for use wherever a Point is expected.
Describing External Types
Interfaces are also used to describe code that is present at runtime, but not implemented in the current TypeScript project. For example, if you open the lib.d.ts file that all TypeScript projects implicitly reference, you'll see an interface declaration for Number:
Now if we have an expression of type Number, the compiler knows that it's valid to call toPrecision on that expression.
Extending Existing Types
Moreover, interfaces in TypeScript are open, meaning you can add your own members to an interface by simply writing another interface block. If you have an external script that adds members to Date, for example, you simply need to write interface Date { /*...*/ } and declare the additional members.*
* Note: There are some known issues with the Visual Studio editor that currently prevent this scenario from working as intended. We'll be fixing this limitation in a later release.
Describing an Indexable Object
A common pattern in JavaScript is to use an object (e.g. {}) as way to map from a set of strings to a set of values. When those values are of the same type, you can use an interface to describe that indexing into an object always produces values of a certain type (in this case, Widget).
Ensuring Class Instance Shape
Let's extend the Greetable example above:
We can implement this interface in a class using the implements keyword:
Now we can use an instance of Person wherever a Greetable is expected:
var g: Greetable = new Person();
Similarly, we can take advantage of the structural typing of TypeScript to implement Greetable in an object literal:
Typescript
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 .
It contains:
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.
Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).
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.
A few basic ones to know:
Pick<Type, Keys>
Only use the specified Keys from the Type.
Partial<Type>
Allows the type to be optional (undefined)
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._
React | Typescript | Tailwind | Forms | Unit Tests
Typescript Types
Everyday Types
In this chapter, we'll cover some of the most common types of values you'll find in JavaScript code, and explain the corresponding ways to describe those types in TypeScript. This isn't an exhaustive list, and future chapters will describe more ways to name and use other types.
Types can also appear in many more places than just type annotations. As we learn about the types themselves, we'll also learn about the places where we can refer to these types to form new constructs.
We'll start by reviewing the most basic and common types you might encounter when writing JavaScript or TypeScript code. These will later form the core building blocks of more complex types.
The primitives:string,number, andboolean
JavaScript has three very commonly used [primitives](https: //developer.mozilla.org/en-US/docs/Glossary/Primitive): string, number, and boolean. Each has a corresponding type in TypeScript. As you might expect, these are the same names you'd see if you used the JavaScript typeof operator on a value of those types:
string represents string values like "Hello, world"
number is for numbers like 42. JavaScript does not have a special runtime value for integers, so there's no equivalent to int or float - everything is simply number
The type names String, Number, and Boolean (starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always use string, number, or boolean for types.
Arrays
To specify the type of an array like [1, 2, 3], you can use the syntax number[]; this syntax works for any type (e.g. string[] is an array of strings, and so on). You may also see this written as Array<number>, which means the same thing. We'll learn more about the syntax T<U> when we cover generics.
Note that [number] is a different thing; refer to the section on [Tuples](https: //www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types).
any
TypeScript also has a special type, any, that you can use whenever you don't want a particular value to cause typechecking errors.
When a value is of type any, you can access any properties of it (which will in turn be of type any), call it like a function, assign it to (or from) a value of any type, or pretty much anything else that's syntactically legal:
The any type is useful when you don't want to write out a long type just to convince TypeScript that a particular line of code is okay.
noImplicitAny
When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default to any.
You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag [noImplicitAny](https: //www.typescriptlang.org/tsconfig#noImplicitAny) to flag any implicit any as an error.
Type Annotations on Variables
When you declare a variable using const, var, or let, you can optionally add a type annotation to explicitly specify the type of the variable:
TypeScript doesn't use "types on the left"-style declarations like int x = 0; Type annotations will always go after the thing being typed.
In most cases, though, this isn't needed. Wherever possible, TypeScript tries to automatically infer the types in your code. For example, the type of a variable is inferred based on the type of its initializer:
For the most part you don't need to explicitly learn the rules of inference. If you're starting out, try using fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully understand what's going on.
Functions
Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the types of both the input and output values of functions.
Parameter Type Annotations
When you declare a function, you can add type annotations after each parameter to declare what types of parameters the function accepts. Parameter type annotations go after the parameter name:
When a parameter has a type annotation, arguments to that function will be checked:
Even if you don't have type annotations on your parameters, TypeScript will still check that you passed the right number of arguments.
Return Type Annotations
You can also add return type annotations. Return type annotations appear after the parameter list:
Much like variable type annotations, you usually don't need a return type annotation because TypeScript will infer the function's return type based on its return statements. The type annotation in the above example doesn't change anything. Some codebases will explicitly specify a return type for documentation purposes, to prevent accidental changes, or just for personal preference.
Anonymous Functions
Anonymous functions are a little bit different from function declarations. When a function appears in a place where TypeScript can determine how it's going to be called, the parameters of that function are automatically given types.
Here's an example:
Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach function, along with the inferred type of the array, to determine the type s will have.
This process is called contextual typing because the context that the function occurred within informs what type it should have.
Similar to the inference rules, you don't need to explicitly learn how this happens, but understanding that it does happen can help you notice when type annotations aren't needed. Later, we'll see more examples of how the context that a value occurs in can affect its type.
Object Types
Apart from primitives, the most common sort of type you'll encounter is an object type. This refers to any JavaScript value with properties, which is almost all of them! To define an object type, we simply list its properties and their types.
For example, here's a function that takes a point-like object:
Here, we annotated the parameter with a type with two properties - x and y - which are both of type number. You can use , or ; to separate the properties, and the last separator is optional either way.
The type part of each property is also optional. If you don't specify a type, it will be assumed to be any.
Optional Properties
Object types can also specify that some or all of their properties are optional. To do this, add a ? after the property name:
In JavaScript, if you access a property that doesn't exist, you'll get the value undefined rather than a runtime error. Because of this, when you read from an optional property, you'll have to check for undefined before using it.
Union Types
TypeScript's type system allows you to build new types out of existing ones using a large variety of operators. Now that we know how to write a few types, it's time to start combining them in interesting ways.
Defining a Union Type
The first way to combine types you might see is a union type. A union type is a type formed from two or more other types, representing values that may be any one of those types. We refer to each of these types as the union's members.
Let's write a function that can operate on strings or numbers:
Working with Union Types
It's easy to provide a value matching a union type - simply provide a type matching any of the union's members. If you have a value of a union type, how do you work with it?
TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union string | number, you can't use methods that are only available on string:
The solution is to narrow the union with code, the same as you would in JavaScript without type annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the structure of the code.
For example, TypeScript knows that only a string value will have a typeof value "string":
Another example is to use a function like Array.isArray:
Notice that in the else branch, we don't need to do anything special - if x wasn't a string[], then it must have been a string.
Sometimes you'll have a union where all the members have something in common. For example, both arrays and strings have a slice method. If every member in a union has a property in common, you can use that property without narrowing:
It might be confusing that a union of types appears to have the intersection of those types' properties. This is not an accident - the name union comes from type theory. The unionnumber | string is composed by taking the union of the values from each type. Notice that given two sets with corresponding facts about each set, only the intersection of those facts applies to the union of the sets themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish speakers wearing hats, after combining those rooms, the only thing we know about every person is that they must be wearing a hat.
Type Aliases
We've been using object types and union types by writing them directly in type annotations. This is convenient, but it's common to want to use the same type more than once and refer to it by a single name.
A type alias is exactly that - a name for any type. The syntax for a type alias is:
You can actually use a type alias to give a name to any type at all, not just an object type. For example, a type alias can name a union type:
Note that aliases are only aliases - you cannot use type aliases to create different/distinct "versions" of the same type. When you use the alias, it's exactly as if you had written the aliased type. In other words, this code might look illegal, but is OK according to TypeScript because both types are aliases for the same type:
Interfaces
An interface declaration is another way to name an object type:
Just like when we used a type alias above, the example works just as if we had used an anonymous object type. TypeScript is only concerned with the structure of the value we passed to printCoord - it only cares that it has the expected properties. Being concerned only with the structure and capabilities of types is why we call TypeScript a structurally typed type system.
Differences Between Type Aliases and Interfaces
Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
|
Extending an interface
|
Extending a type via intersections
| |
Adding new fields to an existing interface
|
A type cannot be changed after being created
|
You'll learn more about these concepts in later chapters, so don't worry if you don't understand all of these right away.
Prior to TypeScript version 4.2, type alias names [may appear in error messages](https: //www.typescriptlang.org/play?#code/PTAEGEHsFsAcEsA2BTATqNrLusgzngIYDm+oA7koqIYuYQJ56gCueyoAUCKAC4AWHAHaFcoSADMaQ0PCG80EwgGNkALk6c5C1EtWgAsqOi1QAb06groEbjWg8vVHOKcAvpokshy3vEgyyMr8kEbQJogAFND2YREAlOaW1soBeJAoAHSIkMTRmbbI8e6aPMiZxJmgACqCGKhY6ABGyDnkFFQ0dIzMbBwCwqIccabcYLyQoKjIEmh8kwN8DLAc5PzwwbLMyAAeK77IACYaQSEjUWZWhfYAjABMAMwALA+gbsVjoADqgjKESytQPxCHghAByXigYgBfr8LAsYj8aQMUASbDQcRSExCeCwFiIQh+AKfAYyBiQFgOPyIaikSGLQo0Zj-aazaY+dSaXjLDgAGXgAC9CKhDqAALxJaw2Ib2RzOISuDycLw+ImBYKQflCkWRRD2LXCw6JCxS1JCdJZHJ5RAFIbFJU8ADKC3WzEcnVZaGYE1ABpFnFOmsFhsil2uoHuzwArO9SmAAEIsSFrZB-GgAjjA5gtVN8VCEc1o1C4Q4AGlR2AwO1EsBQoAAbvB-gJ4HhPgB5aDwem-Ph1TCV3AEEirTp4ELtRbTPD4vwKjOfAuioSQHuDXBcnmgACC+eCONFEs73YAPGGZVT5cRyyhiHh7AAON7lsG3vBggB8XGV3l8-nVISOgghxoLq9i7io-AHsayRWGaFrlFauq2rg9qaIGQHwCBqChtKdgRo8TxRjeyB3o+7xAA), sometimes in place of the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in error messages.
Type aliases may not participate [in declaration merging, but interfaces can](https: //www.typescriptlang.org/play?#code/PTAEEEDtQS0gXApgJwGYEMDGjSfdAIx2UQFoB7AB0UkQBMAoEUfO0Wgd1ADd0AbAK6IAzizp16ALgYM4SNFhwBZdAFtV-UAG8GoPaADmNAcMmhh8ZHAMMAvjLkoM2UCvWad+0ARL0A-GYWVpA29gyY5JAWLJAwGnxmbvGgALzauvpGkCZmAEQAjABMAMwALLkANBl6zABi6DB8okR4Jjg+iPSgABboovDk3jjo5pbW1d6+dGb5djLwAJ7UoABKiJTwjThpnpnGpqPBoTLMAJrkArj4kOTwYmycPOhW6AR8IrDQ8N04wmo4HHQCwYi2Waw2W1S6S8HX8gTGITsQA).
For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type.
Type Assertions
Sometimes you will have information about the type of a value that TypeScript can't know about.
For example, if you're using document.getElementById, TypeScript only knows that this will return some kind of HTMLElement, but you might know that your page will always have an HTMLCanvasElement with a given ID.
In this situation, you can use a type assertion to specify a more specific type:
Like a type annotation, type assertions are removed by the compiler and won't affect the runtime behavior of your code.
You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:
Reminder: Because type assertions are removed at compile-time, there is no runtime checking associated with a type assertion. There won't be an exception or null generated if the type assertion is wrong.
TypeScript only allows type assertions which convert to a more specific or less specific version of a type. This rule prevents "impossible" coercions like:
Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid. If this happens, you can use two assertions, first to any (or unknown, which we'll introduce later), then to the desired type:
Literal Types
In addition to the general types string and number, we can refer to specific strings and numbers in type positions.
One way to think about this is to consider how JavaScript comes with different ways to declare a variable. Both var and let allow for changing what is held inside the variable, and const does not. This is reflected in how TypeScript creates types for literals.
By themselves, literal types aren't very valuable:
It's not much use to have a variable that can only have one value!
But by combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:
Numeric literal types work the same way:
Of course, you can combine these with non-literal types:
There's one more kind of literal type: boolean literals. There are only two boolean literal types, and as you might guess, they are the types true and false. The type boolean itself is actually just an alias for the union true | false.
Literal Inference
When you initialize a variable with an object, TypeScript assumes that the properties of that object might change values later. For example, if you wrote code like this:
TypeScript doesn't assume the assignment of 1 to a field which previously had 0 is an error. Another way of saying this is that obj.counter must have the type number, not 0, because types are used to determine both reading and writing behavior.
The same applies to strings:
In the above example req.method is inferred to be string, not "GET". Because code can be evaluated between the creation of req and the call of handleRequest which could assign a new string like "GUESS" to req.method, TypeScript considers this code to have an error.
There are two ways to work around this.
You can change the inference by adding a type assertion in either location:
The as const suffix acts like const but for the type system, ensuring that all properties are assigned the literal type instead of a more general version like string or number.
nullandundefined
JavaScript has two primitive values used to signal absent or uninitialized value: null and undefined.
TypeScript has two corresponding types by the same names. How these types behave depends on whether you have the [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) option on.
strictNullChecksoff
With [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) off, values that might be null or undefined can still be accessed normally, and the values null and undefined can be assigned to a property of any type. This is similar to how languages without null checks (e.g. C#, Java) behave. The lack of checking for these values tends to be a major source of bugs; we always recommend people turn [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) on if it's practical to do so in their codebase.
strictNullCheckson
With [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks) on, when a value is null or undefined, you will need to test for those values before using methods or properties on that value. Just like checking for undefined before using an optional property, we can use narrowing to check for values that might be null:
Non-null Assertion Operator (Postfix!)
TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing ! after any expression is effectively a type assertion that the value isn't null or undefined:
Just like other type assertions, this doesn't change the runtime behavior of your code, so it's important to only use ! when you know that the value can't be null or undefined.
Enums
Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition to JavaScript but something added to the language and runtime. Because of this, it's a feature which you should know exists, but maybe hold off on using unless you are sure. You can read more about enums in the [Enum reference page](https: //www.typescriptlang.org/docs/handbook/enums.html).
Less Common Primitives
It's worth mentioning the rest of the primitives in JavaScript which are represented in the type system. Though we will not go into depth here.
bigint
From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt:
You can learn more about BigInt in [the TypeScript 3.2 release notes](https: //www.typescriptlang.org/docs/handbook/release-notes/typescript-3-2.html#bigint).
symbol
There is a primitive in JavaScript used to create a globally unique reference via the function Symbol():
You can learn more about them in [Symbols reference page](https: //www.typescriptlang.org/docs/handbook/symbols.html).
There was an idea for some type of autocomplete, but it was omitted from the MVP version we launched with. Early step in re-visiting this would be to 1. check that this type of thing is still desirable, 2. research what is possible with the SOLR search platform
\
like 1
Abstract
\
Types VS Interfaces
Object Types
In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript, we represent those through object types.
As we've seen, they can be anonymous:
or they can be named by using either an interface
or a type alias.
In all three examples above, we've written functions that take objects that contain the property name (which must be a string) and age (which must be a number).
Property Modifiers
Each property in an object type can specify a couple of things: the type, whether the property is optional, and whether the property can be written to.
Optional Properties
Much of the time, we'll find ourselves dealing with objects that might have a property set. In those cases, we can mark those properties as optional by adding a question mark (?) to the end of their names.
In this example, both xPos and yPos are considered optional. We can choose to provide either of them, so every call above to paintShape is valid. All optionality really says is that if the property is set, it better have a specific type.
We can also read from those properties - but when we do under [strictNullChecks](https: //www.typescriptlang.org/tsconfig#strictNullChecks), TypeScript will tell us they're potentially undefined.
In JavaScript, even if the property has never been set, we can still access it - it's just going to give us the value undefined. We can just handle undefined specially.
Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to support it.
Here we used [a destructuring pattern](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) for paintShape's parameter, and provided [default values](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Default_values) for xPos and yPos. Now xPos and yPos are both definitely present within the body of paintShape, but optional for any callers to paintShape.
Note that there is currently no way to place type annotations within destructuring patterns. This is because the following syntax already means something different in JavaScript.
In an object destructuring pattern, shape: Shape means "grab the property shape and redefine it locally as a variable named Shape. Likewise xPos: number creates a variable named number whose value is based on the parameter's xPos.
Using [mapping modifiers](https: //www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers), you can remove optional attributes.
readonlyProperties
Properties can also be marked as readonly for TypeScript. While it won't change any behavior at runtime, a property marked as readonly can't be written to during type-checking.
Using the readonly modifier doesn't necessarily imply that a value is totally immutable - or in other words, that its internal contents can't be changed. It just means the property itself can't be re-written to.
It's important to manage expectations of what readonly implies. It's useful to signal intent during development time for TypeScript on how an object should be used. TypeScript doesn't factor in whether properties on two types are readonly when checking whether those types are compatible, so readonly properties can also change via aliasing.
Using [mapping modifiers](https: //www.typescriptlang.org/docs/handbook/2/mapped-types.html#mapping-modifiers), you can remove readonly attributes.
Index Signatures
Sometimes you don't know all the names of a type's properties ahead of time, but you do know the shape of the values.
In those cases you can use an index signature to describe the types of possible values, for example:
Above, we have a StringArray interface which has an index signature. This index signature states that when a StringArray is indexed with a number, it will return a string.
An index signature property type must be either ‘string' or ‘number'.
It is possible to support both types of indexers...
While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that all properties match their return type. This is because a string index declares that obj.property is also available as obj["property"]. In the following example, name's type does not match the string index's type, and the type checker gives an error:
However, properties of different types are acceptable if the index signature is a union of the property types:
Finally, you can make index signatures readonly in order to prevent assignment to their indices:
You can't set myArray[2] because the index signature is readonly.
Extending Types
It's pretty common to have types that might be more specific versions of other types. For example, we might have a BasicAddress type that describes the fields necessary for sending letters and packages in the U.S.
In some situations that's enough, but addresses often have a unit number associated with them if the building at an address has multiple units. We can then describe an AddressWithUnit.
This does the job, but the downside here is that we had to repeat all the other fields from BasicAddress when our changes were purely additive. Instead, we can extend the original BasicAddress type and just add the new fields that are unique to AddressWithUnit.
The extends keyword on an interface allows us to effectively copy members from other named types, and add whatever new members we want. This can be useful for cutting down the amount of type declaration boilerplate we have to write, and for signaling intent that several different declarations of the same property might be related. For example, AddressWithUnit didn't need to repeat the street property, and because street originates from BasicAddress, a reader will know that those two types are related in some way.
interfaces can also extend from multiple types.
Intersection Types
interfaces allowed us to build up new types from other types by extending them. TypeScript provides another construct called intersection types that is mainly used to combine existing object types.
An intersection type is defined using the & operator.
Here, we've intersected Colorful and Circle to produce a new type that has all the members of ColorfulandCircle.
Interfaces vs. Intersections
We just looked at two ways to combine types which are similar, but are actually subtly different. With interfaces, we could use an extends clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias. The principle difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you'd pick one over the other between an interface and a type alias of an intersection type.
Generic Object Types
Let's imagine a Box type that can contain any value - strings, numbers, Giraffes, whatever.
Right now, the contents property is typed as any, which works, but can lead to accidents down the line.
We could instead use unknown, but that would mean that in cases where we already know the type of contents, we'd need to do precautionary checks, or use error-prone type assertions.
One type safe approach would be to instead scaffold out different Box types for every type of contents.
But that means we'll have to create different functions, or overloads of functions, to operate on these types.
That's a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frustrating, since our box types and overloads are all effectively the same.
Instead, we can make a genericBox type which declares a type parameter.
You might read this as "A Box of Type is something whose contents have type Type". Later on, when we refer to Box, we have to give a type argument in place of Type.
Think of Box as a template for a real type, where Type is a placeholder that will get replaced with some other type. When TypeScript sees Box<string>, it will replace every instance of Type in Box<Type> with string, and end up working with something like { contents: string }. In other words, Box<string> and our earlier StringBox work identically.
Box is reusable in that Type can be substituted with anything. That means that when we need a box for a new type, we don't need to declare a new Box type at all (though we certainly could if we wanted to).
This also means that we can avoid overloads entirely by instead using [generic functions](https: //www.typescriptlang.org/docs/handbook/2/functions.html#generic-functions).
It is worth noting that type aliases can also be generic. We could have defined our new Box<Type> interface, which was:
by using a type alias instead:
Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.
We'll circle back to type aliases in just a little bit.
TheArrayType
Generic object types are often some sort of container type that work independently of the type of elements they contain. It's ideal for data structures to work this way so that they're re-usable across different data types.
It turns out we've been working with a type just like that throughout this handbook: the Array type. Whenever we write out types like number[] or string[], that's really just a shorthand for Array<number> and Array<string>.
Much like the Box type above, Array itself is a generic type.
Modern JavaScript also provides other data structures which are generic, like Map<K, V>, Set<T>, and Promise<T>. All this really means is that because of how Map, Set, and Promise behave, they can work with any sets of types.
TheReadonlyArrayType
The ReadonlyArray is a special type that describes arrays that shouldn't be changed.
Much like the readonly modifier for properties, it's mainly a tool we can use for intent. When we see a function that returns ReadonlyArrays, it tells us we're not meant to change the contents at all, and when we see a function that consumes ReadonlyArrays, it tells us that we can pass any array into that function without worrying that it will change its contents.
Unlike Array, there isn't a ReadonlyArray constructor that we can use.
Instead, we can assign regular Arrays to ReadonlyArrays.
Just as TypeScript provides a shorthand syntax for Array<Type> with Type[], it also provides a shorthand syntax for ReadonlyArray<Type> with readonly Type[].
One last thing to note is that unlike the readonly property modifier, assignability isn't bidirectional between regular Arrays and ReadonlyArrays.
Tuple Types
A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions.
Here, StringNumberPair is a tuple type of string and number. Like ReadonlyArray, it has no representation at runtime, but is significant to TypeScript. To the type system, StringNumberPair describes arrays whose 0 index contains a string and whose 1 index contains a number.
If we try to index past the number of elements, we'll get an error.
We can also [destructure tuples](https: //developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment#Array_destructuring) using JavaScript's array destructuring.
Tuple types are useful in heavily convention-based APIs, where each element's meaning is "obvious". This gives us flexibility in whatever we want to name our variables when we destructure them. In the above example, we were able to name elements 0 and 1 to whatever we wanted.
However, since not every user holds the same view of what's obvious, it may be worth reconsidering whether using objects with descriptive property names may be better for your API.
Other than those length checks, simple tuple types like these are equivalent to types which are versions of Arrays that declare properties for specific indexes, and that declare length with a numeric literal type.
Another thing you may be interested in is that tuples can have optional properties by writing out a question mark (? after an element's type). Optional tuple elements can only come at the end, and also affect the type of length.
Tuples can also have rest elements, which have to be an array/tuple type.
StringNumberBooleans describes a tuple whose first two elements are string and number respectively, but which may have any number of booleans following.
StringBooleansNumber describes a tuple whose first element is string and then any number of
A tuple with a rest element has no set "length" - it only has a set of well-known elements in different positions.
Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with parameter lists. Tuples types can be used in [rest parameters and arguments](https: //www.typescriptlang.org/docs/handbook/2/functions.html#rest-parameters-and-arguments), so that the following:
is basically equivalent to:
This is handy when you want to take a variable number of arguments with a rest parameter, and you need a minimum number of elements, but you don't want to introduce intermediate variables.
readonlyTuple Types
One final note about tuple types - tuples types have readonly variants, and can be specified by sticking a readonly modifier in front of them - just like with array shorthand syntax.
As you might expect, writing to any property of a readonly tuple isn't allowed in TypeScript.
Tuples tend to be created and left un-modified in most code, so annotating types as readonly tuples when possible is a good default. This is also important given that array literals with const assertions will be inferred with readonly tuple types.
Here, distanceFromOrigin never modifies its elements, but expects a mutable tuple. Since point's type was inferred as readonly [3, 4], it won't be compatible with [number, number] since that type can't guarantee point's elements won't be mutated.
Enums
Enums
Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.
Enums allow a developer to define a set of named constants. Using enums can make it easier to document intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.
Numeric enums
We'll first start off with numeric enums, which are probably more familiar if you're coming from other languages. An enum can be defined using the enum keyword.
Above, we have a numeric enum where Up is initialized with 1 . All of the following members are auto-incremented from that point on. In other words, Direction.Up has the value 1 , Down has 2 , Left has 3 , and Right has 4 .
If we wanted, we could leave off the initializers entirely:
Here, Up would have the value 0 , Down would have 1 , etc. This auto-incrementing behavior is useful for cases where we might not care about the member values themselves, but do care that each value is distinct from other values in the same enum.
Using an enum is simple: just access any member as a property off of the enum itself, and declare types using the name of the enum:
Numeric enums can be mixed in [computed and constant members (see below)](https: //www.typescriptlang.org/docs/handbook/enums.html#computed-and- constant-members). The short story is, enums without initializers either need to be first, or have to come after numeric enums initialized with numeric constants or other constant enum members. In other words, the following isn't allowed:
String enums
String enums are a similar concept, but have some subtle [runtime differences](https: //www.typescriptlang.org/docs/handbook/enums.html#enums-at-runtime) as documented below. In a string enum, each member has to be constant-initialized with a string literal, or with another string enum member.
While string enums don't have auto-incrementing behavior, string enums have the benefit that they "serialize" well. In other words, if you were debugging and had to read the runtime value of a numeric enum, the value is often opaque - it doesn't convey any useful meaning on its own (though [reverse mapping](https: //www.typescriptlang.org/docs/handbook/enums.html#reverse-mappings) can often help). String enums allow you to give a meaningful and readable value when your code runs, independent of the name of the enum member itself.
Heterogeneous enums
Technically enums can be mixed with string and numeric members, but it's not clear why you would ever want to do so:
Unless you're really trying to take advantage of JavaScript's runtime behavior in a clever way, it's advised that you don't do this.
Computed and
constant members
Each enum member has a value associated with it which can be either _ constant_ or computed. An enum member is considered constant if:
It is the first member in the enum and it has no initializer, in which case it's assigned the value 0:
The enum member is initialized with a constant enum expression. A constant enum expression is a subset of TypeScript expressions that can be fully evaluated at compile time. An expression is a constant enum expression if it is:
a literal enum expression (basically a string literal or a numeric literal)
a reference to previously defined constant enum member (which can originate from a different enum)
In all other cases enum member is considered computed.
Union enums and enum member types
There is a special subset of constant enum members that aren't calculated: literal enum members. A literal enum member is a constant enum member with no initialized value, or with values that are initialized to
any string literal (e.g. "foo", "bar, "baz")
any numeric literal (e.g. 1, 100)
When all members in an enum have literal enum values, some special semantics come into play.
The first is that enum members also become types as well! For example, we can say that certain members can only have the value of an enum member:
The other change is that enum types themselves effectively become a union of each enum member. With union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in the enum itself. Because of that, TypeScript can catch bugs where we might be comparing values incorrectly. For example:
In that example, we first checked whether x was notE.Foo . If that check succeeds, then our || will short-circuit, and the body of the ‘if' will run. However, if the check didn't succeed, then x can only be E.Foo , so it doesn't make sense to see whether it's equal to E.Bar .
Enums at runtime
Enums are real objects that exist at runtime. For example, the following enum
can actually be passed around to functions
Enums at compile time
Even though Enums are real objects that exist at runtime, the keyof keyword works differently than you might expect for typical objects. Instead, use keyof typeof to get a Type that represents all Enum keys as strings.
Reverse mappings
In addition to creating an object with property names for members, numeric enums members also get a reverse mapping from enum values to enum names. For example, in this example:
TypeScript compiles this down to the following JavaScript:
In this generated code, an enum is compiled into an object that stores both forward ( name -> value ) and reverse ( value -> name ) mappings. References to other enum members are always emitted as property accesses and never inlined.
Keep in mind that string enum members do not get a reverse mapping generated at all.
`
const` enums
In most cases, enums are a perfectly valid solution. However sometimes requirements are tighter. To avoid paying the cost of extra generated code and additional indirection when accessing enum values, it's possible to use const enums. const enums are defined using the const modifier on our enums:
const enums can only use constant enum expressions and unlike regular enums they are completely removed during compilation. const enum members are inlined at use sites. This is possible since const enums cannot have computed members.
in generated code will become
** const enum pitfalls**
Inlining enum values is straightforward at first, but comes with subtle implications. These pitfalls pertain to ambient const enums only (basically const enums in .d.ts files) and sharing them between projects, but if you are publishing or consuming .d.ts files, these pitfalls likely apply to you, because tsc --declaration transforms .ts files into .d.ts files.
For the reasons laid out in the [isolatedModules documentation](https: //www.typescriptlang.org/tsconfig#references-to- const-enum-members), that mode is fundamentally incompatible with ambient const enums. This means if you publish ambient const enums, downstream consumers will not be able to use [isolatedModules](https: //www.typescriptlang.org/tsconfig#isolatedModules) and those enum values at the same time.
You can easily inline values from version A of a dependency at compile time, and import version B at runtime. Version A and B's enums can have different values, if you are not very careful, resulting in [surprising bugs](https: //github.com/microsoft/TypeScript/issues/5219#issue-110947903), like taking the wrong branches of if statements. These bugs are especially pernicious because it is common to run automated tests at roughly the same time as projects are built, with the same dependency versions, which misses these bugs completely.
Here are two approaches to avoiding these pitfalls:
A. Do not use const enums at all. You can easily [ban const enums](https: //github.com/typescript-eslint/typescript-eslint/blob/master/docs/getting-started/linting/FAQ.md#how-can-i-ban-specific-language-feature) with the help of a linter. Obviously this avoids any issues with const enums, but prevents your project from inlining its own enums. Unlike inlining enums from other projects, inlining a project's own enums is not problematic and has performance implications. B. Do not publish ambient const enums, by de constifying them with the help of [preserve constEnums ](https: //www.typescriptlang.org/tsconfig#preserve constEnums). This is the approach taken internally by the [TypeScript project itself](https: //github.com/microsoft/TypeScript/pull/5422). [preserve constEnums ](https: //www.typescriptlang.org/tsconfig#preserve constEnums)emits the same JavaScript for const enums as plain enums. You can then safely strip the const modifier from .d.ts files [in a build step](https: //github.com/microsoft/TypeScript/blob/1a981d1df1810c868a66b3828497f049a944951c/Gulpfile.js#L144).
This way downstream consumers will not inline enums from your project, avoiding the pitfalls above, but a project can still inline its own enums, unlike banning const enums entirely.
Ambient enums
Ambient enums are used to describe the shape of already existing enum types.
One important difference between ambient and non-ambient enums is that, in regular enums, members that don't have an initializer will be considered constant if its preceding enum member is considered constant. By contrast, an ambient (and non- const) enum member that does not have an initializer is always considered computed.
Objects vs Enums
In modern TypeScript, you may not need an enum when an object with as const could suffice:
\
React
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
\
\
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
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.
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.
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
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
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:
\
\
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.
\
\
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
Accessability
Understanding Techniques for WCAG Success Criteria
Introduction to Web Accessibility | Web Accessibility Initiative (WAI) | W3C
Excerpt
The Website of the World Wide Web Consortium’s Web Accessibility Initiative.
Page Contents
Accessibility in Context
The power of the Web is in its universality.
Access by everyone regardless of disability is an essential aspect.
The Web is fundamentally designed to work for all people, whatever their hardware, software, language, location, or ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability.
Thus the impact of disability is radically changed on the Web because the Web removes barriers to communication and interaction that many people face in the physical world. However, when websites, applications, technologies, or tools are badly designed, they can create barriers that exclude people from using the Web.
Accessibility is essential for developers and organizations that want to create high-quality websites and web tools, and not exclude people from using their products and services.
What is Web Accessibility
Web accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can:
perceive, understand, navigate, and interact with the Web
contribute to the Web
Web accessibility encompasses all disabilities that affect access to the Web, including:
auditory
cognitive
neurological
Web accessibility also benefits people without disabilities, for example:
people using mobile phones, smart watches, smart TVs, and other devices with small screens, different input modes, etc.
older people with changing abilities due to ageing
people with “temporary disabilities” such as a broken arm or lost glasses
For a 7-minute video with examples of how accessibility is essential for people with disabilities and useful for everyone in a variety of situations, see:
Accessibility is Important for Individuals, Businesses, Society
The Web is an increasingly important resource in many aspects of life: education, employment, government, commerce, health care, recreation, and more. It is essential that the Web be accessible in order to provide equal access and equal opportunity to people with diverse abilities. Access to information and communications technologies, including the Web, is defined as a basic human right in the United Nations Convention on the Rights of Persons with Disabilities (UN ).
The Web offers the possibility of unprecedented access to information and interaction for many people with disabilities. That is, the accessibility barriers to print, audio, and visual media can be much more easily overcome through web technologies.
Accessibility supports social inclusion for people with disabilities as well as others, such as:
older people
people in rural areas
people in developing countries
There is also a strong business case for accessibility. As shown in the previous section, accessible design improves overall user experience and satisfaction, especially in a variety of situations, across different devices, and for older users. Accessibility can enhance your brand, drive innovation, and extend your market reach.
Web accessibility is required by law in many situations.
Making the Web Accessible
Web accessibility depends on several components working together, including web technologies, web browsers and other "user agents", authoring tools, and websites.
The W3C Web Accessibility Initiative () develops technical specifications, guidelines, techniques, and supporting resources that describe accessibility solutions. These are considered international standards for web accessibility; for example, WCAG 2.0 is also an ISO standard: ISO/IEC 40500.
Making Your Website Accessible
Many aspects of accessibility are fairly easy to understand and implement. Some accessibility solutions are more complex and take more knowledge to implement.
It is most efficient and effective to incorporate accessibility from the very beginning of projects, so you don’t need go back and to re-do work.
Evaluating Accessibility
When developing or redesigning a website, evaluate accessibility early and throughout the development process to identify accessibility problems early, when it is easier to address them. Simple steps, such as changing settings in a browser, can help you evaluate some aspects of accessibility. Comprehensive evaluation to determine if a website meets all accessibility guidelines takes more effort.
There are evaluation tools that help with evaluation. However, no tool alone can determine if a site meets accessibility guidelines. Knowledgeable human evaluation is required to determine if a site is accessible.
Examples
Alternative Text for Images
Images should include (alt text) in the markup/code.
If alt text isn’t provided for images, the image information is inaccessible, for example, to people who cannot see and use a screen reader that reads aloud the information on a page, including the alt text for the visual image.
When equivalent alt text is provided, the information is available to people who are blind, as well as to people who turn off images (for example, in areas with expensive or low bandwidth). It’s also available to technologies that cannot see images, such as search engines.
Keyboard Input
Some people cannot use a mouse, including many older users with limited fine motor control. An accessible website does not rely on the mouse; it makes . Then people with disabilities can use that mimic the keyboard, such as speech input.
Transcripts for Audio
Just as images aren’t available to people who can’t see, audio files aren’t available to people who can’t hear. Providing a text transcript makes the audio information accessible to people who are deaf or hard of hearing, as well as to search engines and other technologies that can’t hear.
It’s easy and relatively inexpensive for websites to provide transcripts. There are also that create text transcripts in HTML format.
For More Information
W3C WAI provides a wide range of resources on different aspects of web accessibility , , , . We encourage you to explore this website, or look through the list.
Excerpt
WCAG 2.1 guidelines and success criteria are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc. They are stable and do not change.
WCAG 2.1 guidelines and success criteria are designed to be broadly applicable to current and future web technologies, including dynamic applications, mobile, digital television, etc. They are stable and do not change.
Specific guidance for authors and evaluators on meeting the WCAG success criteria is provided in techniques, which include code examples, resources, and tests. W3C's document is updated periodically, about twice per year, to cover more current best practices and changes in technologies and tools.
The three types of guidance in are explained below:
Sufficient techniques
Advisory techniques
Failures
Also explained below:
General and technology-specific techniques - which can be sufficient or advisory
Other techniques - beyond what is in W3C's published document
Technique tests
provides related information, including on .
Techniques are Informative
Techniques are informative—that means they are not required. The basis for determining conformance to WCAG 2.1 is the success criteria from the WCAG 2.1 standard—not the techniques.
Note 1: W3C cautions against requiring W3C's sufficient techniques. The only thing that should be required is meeting the WCAG 2.1 success criteria. To learn more, see:
in the WCAG 2 FAQ
Note 2: uses the words "must" and "should" only to clarify guidance within the techniques, not to convey requirements for WCAG.
Sufficient Techniques
Sufficient techniques are reliable ways to meet the success criteria.
From an author's perspective: If you use the sufficient techniques for a given criterion correctly and it is for your users, you can be confident that you met the success criterion.
From an evaluator's perspective: If web content implements the sufficient techniques for a given criterion correctly and it is for the content's users, it conforms to that success criterion. (The converse is not true; if content does not implement these sufficient techniques, it does not necessarily fail the success criteria, as explained in below.)
There may be other ways to meet success criteria besides the sufficient techniques in W3C's document, as explained in below. (See alsoabove.)
Numbered Lists, "AND"
The W3C-documented sufficient techniques are provided in a numbered list where each list item provides a technique or combination of techniques that can be used to meet the success criterion. Where there are multiple techniques on a numbered list item connected by "AND" then all of the techniques must be used to be sufficient. For example, has: "G115: Using semantic elements to mark up structure AND H49: Using semantic markup to mark emphasized or special text (HTML)".
Advisory Techniques
Advisory techniques are suggested ways to improve accessibility. They are often very helpful to some users, and may be the only way that some users can access some types of content.
Advisory techniques are not designated as sufficient techniques for various reasons such as:
they may not be sufficient to meet the full requirements of the success criteria;
they may be based on technology that is not yet stable;
they may not be in many cases (for example, assistive technologies do not work with them yet);
Authors are encouraged to apply all of the techniques where appropriate to best address the widest range of users' needs.
Failures
Failures are things that cause accessibility barriers and fail specific success criteria. The documented failures are useful for:
Authors to know what to avoid,
Evaluators to use for checking if content does not meet WCAG success criteria.
Content that has a failure does not meet WCAG success criteria, unless an alternate version is provided without the failure.
If anyone identifies a situation where a documented failure is not correct, please so that it can be corrected or deleted as appropriate.
General and Technology-specific Techniques
General techniques describe basic practices that apply to all technologies. Technology-specific techniques apply to a specific technology.
Some success criteria do not have technology-specific techniques and are covered only with general techniques. Therefore, both the general techniques and the relevant technology-specific techniques should be considered.
Publication of techniques for a specific technology does not imply that the technology can be used in all situations to create content that meets WCAG 2.1 success criteria and conformance requirements. Developers need to be aware of the limitations of specific technologies and provide content in a way that is accessible to people with disabilities.
Other Techniques
In addition to the techniques in W3C's document, there are other ways to meet WCAG success criteria. W3C's techniques are not comprehensive and may not cover newer technologies and situations.
Web content does not have to use W3C's published techniques in order to conform to WCAG 2.1.__(See alsoabove.)
Content authors can develop different techniques. For example, an author could develop a technique for HTML5, , or other new technology. Other organizations may develop sets of techniques to meet WCAG 2.1 success criteria.
Any techniques can be sufficient if:
they satisfy the success criterion, and
all of the are met.
Submitting Techniques
The WCAG Working Group encourages people to submit new techniques so that they can be considered for inclusion in updates of the document. Please submit techniques for consideration using the .
Testing Techniques
Each technique has tests that help:
authors verify that they implemented the technique properly, and
evaluators determine if web content meets the technique.
The tests are only for a technique, they are not tests for conformance to WCAG success criteria.
Failing a technique test does not necessarily mean failing WCAG, because the techniques are discrete (that is, they address one specific point) and they are not required.
Content can meet WCAG success criteria in different ways other than W3C's published sufficient techniques.
Content that passes the sufficient techniques for a specific technology does not necessarily meet all WCAG success criteria. Some success criteria have only general techniques, not technology-specific techniques.
Thus while the techniques are useful for evaluating content, evaluations must go beyond just checking the sufficient technique tests in order to evaluate how content conforms to WCAG success criteria.
Failures are particularly useful for evaluations because they do indicate non-conformance (unless an alternate version is provided without the failure).
User Agent and Assistive Technology Support Notes
Some techniques require that web content users have specific browsers or assistive technologies in order for the technique to be . The User Agent and Assistive Technology Support Notes sections of individual techniques include some information to help determine accessibility support.
Support Notes Change Over Time
As time passes, the versions of user agents (browsers, etc.) or assistive technologies listed may not be the current versions. The Working Group may not update most of these notes as new versions are released. Authors should test techniques with the user agents and assistive technologies currently available to their users. See also .
Using the Techniques
is not intended to be used as a stand-alone document. Instead, it is expected that content authors will usually use to read the WCAG success criteria, and follow links from there to specific topics in Understanding WCAG 2.1 and to specific techniques.
Alternatives must meet success criteria
Some techniques describe how to provide alternate ways for users to get content. For example, mentions a transcript as an alternative for an audio file. Some alternatives must also conform to WCAG. For example, the transcript itself must meet all relevant success criteria.
Example Code
The code examples in the techniques are intended to demonstrate only the specific point discussed in the technique. They might not demonstrate best practice for other aspects of accessibility, usability, or coding not related to the technique. They are not intended to be copied and used as the basis for developing web content.
Many techniques point to "working examples" that are more robust and may be appropriate for copying and integrating into web content.
Sitecore Config
The purpose of this spreadsheet is to allow values to be changed in lower environments. After EVERY deployment, the lower environments (Test, QA, etc) have their master databases restored from Production. That is great news that most of the conten is fresh but one issue is that any values that are hard coded for production, are also hard coded for the lower environments. There needs to be a way to modify these "production" values to their lower environment equivalents. That is where this spreadsheet comes in. This spreadsheet is used against each new database restore to update the production values to their lower environmetn equivalents.
Item - The full sitecore path to the item that needs to be modified.
GUID - The GUID to the item that needs to be modified.
Typescript
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.
project.d.ts
It contains:
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.
Some library module declarations (usually these are included because these libs don't have typings but we still need to use them).
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.
Utility Types
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:
Pick<Type, Keys>
Only use the specified Keys from the Type.
Partial<Type>
Allows the type to be optional (undefined)
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._
Naming
class
🧑🔬 PascalCase
🚫 Bad
✅ Good
For memebers/methods use 🐪 camelCase
🚫 Bad
✅ Good
enum
🧑🔬 PascalCase
🚫 Bad
✅ Good
interface
🧑🔬 PascalCase
🚫 Bad
✅ Good
For memebers use 🐪 camelCase
🚫 Bad
✅ Good
namespace
🧑🔬 PascalCase
🚫 Bad
✅ Good
type
🧑🔬 PascalCase
🚫 Bad
✅ ✅ Good
variable and function
🐪 camelCase
🚫 Bad
✅ Good
React | Typescript | Tailwind | Forms | Unit Tests
Focus Order
Understanding Success Criterion 2.4.3: Focus Order
Excerpt
The intent of this Success Criterion is to ensure that when users navigate sequentially through content, they encounter information in an order that is consistent with the meaning of the content and can be operated from the keyboard. This reduces confusion by letting users form a consistent mental model of the content. There may be different orders that reflect logical relationships in the content. For example, moving through components in a table one row at a time or one column at a time both reflect the logical relationships in the content. Either order may satisfy this Success Criterion.
Unknown Prop Warning
The unknown-prop warning will fire if you attempt to render a DOM element with a prop that is not recognized by React as a legal DOM attribute/property. You should ensure that your DOM elements do not have spurious props floating around.
There are a couple of likely reasons this warning could be appearing:
Are you using {...this.props} or cloneElement(element, this.props)? Your component is transferring its own props directly to a child element (eg. transferring props). When transferring props to a child component, you should ensure that you are not accidentally forwarding props that were intended to be interpreted by the parent component.
Don't Call PropTypes Warning
Note:
React.PropTypes has moved into a different package since React v15.5. Please use .
We provide a codemod script to automate the conversion.
In a future major release of React, the code that implements PropType validation functions will be stripped in production. Once this happens, any code that calls these functions manually (that isn't stripped in production) will throw an error.
React Element Factories and JSX Warning
You probably came here because your code is calling your component as a plain function call. This is now deprecated:
JSX
React components can no longer be called directly like this. Instead you can use JSX.
- `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 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],
};
};