Chapter 08
Build the button
The moment you have been waiting for. There is not much code, and every line has a reason. First the link between the settings file and the code, then the code itself, one idea at a time.
Pega 24 – 26
Words you will meet
- React
- TypeScript
- withConfiguration
The key idea
A setting in the file is a prop in the code.
A propA value handed to a React component from outside, like an ingredient handed to a chef. is something a component is given. Pega gives your component one prop for each entry in config.json, with the same name. So buttonText in the file arrives as props.buttonText in the code. Nothing else connects them. Get the spelling identical and the rest follows.
Modern DXCB releases include bundled type definitions via @pega/pcore-pconnect-typedefs and pcore-globals.d.ts, giving you full TypeScript autocomplete and strict checking for PCore and PConnect methods.
config.json · what you write
{ "properties": [ { "name": "buttonText", "label": "Button text", "format": "TEXT" }, { "name": "celebrationText", "label": "Celebration message", "format": "TEXT" }, { "name": "visibility", "label": "Visible", "format": "VISIBILITY" }, { "name": "disabled", "label": "Disabled", "format": "DISABLED" } ]}Each property's name must match a prop in the component. The format picks the kind of control Pega draws for it.
What the Pega designer sees
The button on the screen
Walkthrough
The code, one idea at a time.
Here is the whole of index.tsx. Step through it: the highlighted lines on the left are explained on the right.
// ── 1. Imports ──────────────────────────────────────import { useEffect, useState } from 'react';import { Button, withConfiguration } from '@pega/cosmos-react-core';import type { PConnProps } from './PConnProps';import './create-nonce';import StyledAcmeUICelebrateButtonWrapper from './styles'; // ── 2. Helpers & Constants ──────────────────────────// Shown when a designer leaves "Button text" empty, so the button is never blank.const DEFAULT_TEXT = 'Celebrate'; // ── 3. Main Component ───────────────────────────────interface AcmeUICelebrateButtonProps extends PConnProps { buttonText?: string; celebrationText?: string; visibility?: boolean; disabled?: boolean;} function AcmeUICelebrateButton(props: AcmeUICelebrateButtonProps) { // 3.1 Props with defaults const { buttonText, celebrationText = 'Well done!', visibility = true, disabled = false } = props; // 3.2 State const [celebrating, setCelebrating] = useState(false); // Hide the message after a moment. The cleanup stops a timer firing after the button is gone. useEffect(() => { if (!celebrating) return undefined; const timer = setTimeout(() => setCelebrating(false), 2500); return () => clearTimeout(timer); }, [celebrating]); // 3.3 Handlers const handleClick = () => { if (disabled) return; setCelebrating(true); }; // 3.4 Early return: Pega can ask a component to hide itself. if (!visibility) return null; // 3.5 Render return ( <StyledAcmeUICelebrateButtonWrapper> <Button variant='primary' disabled={disabled} onClick={handleClick}> {buttonText?.trim() || DEFAULT_TEXT} </Button> {celebrating && <p role='status'>{celebrationText}</p>} </StyledAcmeUICelebrateButtonWrapper> );} // ── 4. Exports ──────────────────────────────────────// Default export: wrapped so Pega can feed it its settings at runtime.export default withConfiguration(AcmeUICelebrateButton);// Named export: the bare component, for tests and Storybook, with no Pega around it.export { AcmeUICelebrateButton };Step 1 of 8
Bring in the tools
Import what the button needs: React’s state and effect helpers, Pega’s own Button and withConfiguration from the Cosmos design system, the type vocabulary, the nonce helper and your styles. Importing Pega’s Button means ours looks and behaves like every other button in Constellation.
Appearance
The looks, in their own file.
Styles live in styles.ts and are written with styled-componentsA library that lets you write CSS inside TypeScript, attached to one component, so styles never leak onto the rest of the page.. The button itself comes from Pega's design system, so it already looks right. All the wrapper adds is a little spacing, and a promise to use whatever font the page uses.
import styled from 'styled-components'; const StyledAcmeUICelebrateButtonWrapper = styled.div` margin: 0; /* Use Pega's own font from the page instead of naming one, so the button always matches the app. */ button { font-family: inherit; } p { margin: 0.5rem 0 0; }`; export default StyledAcmeUICelebrateButtonWrapper;Doing it properly
The house standard.
Teams add rules so that any component can be read by anyone. This project's are short: four numbered sections in every index.tsx, named imports from React, comments that explain why rather than what, and two exports. The one detail the walkthrough simplified: in this project the default export also wears an error boundary.
// Default export: wrapped with Pega's runtime plumbing AND an error boundary,// so one broken button cannot blank the whole page.export default withConfiguration(withErrorBoundary(AcmeUICelebrateButton, 'Acme_UI_CelebrateButton')); // Named export: the raw component for tests and Storybook, with no Pega around it.export { AcmeUICelebrateButton };The component is written. Now to see it, without needing a Pega server at all.
Your turn
Questions and comments
Stuck on something, or have a thought? Write it here. To talk about one part, use the Comment button under that section's heading.