gg logo

gg

Documentation

Changelog

Blog

Using components

→ Click here to see the state of the app after completing this step.

→ Click here to see the changes we'll make in this step in GitHub.


Looking at the current markup, we notice that we have to add the button class to every single button. Everytime we want to add a new button we also have to remember to add the class to the element. While it seems simple enough for our small app, this problem could become a real pain in bigger websites.

In gg we can use components to solve this. A component encapsulates some markup and/or some logic and we can reuse the component in as many places as we like without having to write duplicate code.

So let's create a component that will show a single button.

2 +3const Button = gg.component(() => {+4 return <button>click me</button>;+5});+6 7const stylesheet = gg.stylesheet(`

Right now the button returned by this component always contains the same text. We have to make the text in the button configurable so that we can somehow pass it to the component when we use it.

We can use children for that. Everything that we put between the opening and the closing tag of our custom component will become its children.

// No children<Button /> // Just text<Button>click me</Button> // Other HTML elements<Button> <span>click</span> me</Button> // Other components<Button> <SomeOtherComponent /></Button>

We have to tell the component what it should do with the children that are passed to it. gg will call the function we passed to the component method with an object commonly called args. This object contains a key called children that we can use when creating the markup that the component returns.

2 -const Button = gg.component(() => {- return <button>click me</button>;+3const Button = gg.component((args) => {+4 return <button>{args.children}</button>; 5});

We also need to attach the classes that we currently added to the button elements. Adding the .button class is easy enough, because every button should have this class.

3const Button = gg.component((args) => {- return <button>{args.children}</button>;+4 return (+5 <button class={[stylesheet.getClass("button")]}>{args.children}</button>+6 ); 7});

However, the zero-button also has an additional class. There are two common ways to extend our Button component so that it can handle this use-case. Both involve custom arguments.

The first way is to be able to pass down additional class names when using the component. Let's make the Button aware of the fact that we want to pass a custom argument to it using TypeScript.

2 +3type ButtonArgs = {+4 class?: string[];+5};+6 -const Button = gg.component((args) => {+7const Button = gg.component<ButtonArgs>((args) => { 8 return (

Note that we defined the custom argument as optional in the ButtonArgs type. That way we don't need to pass it everytime we want to use the Button component, only in those cases where we actually want to add an additional class.

Now we can access our custom argument in the args object. If there are additional classes we add them to the array we pass as attribute to the button element.

8 return (- <button class={[stylesheet.getClass("button")]}>{args.children}</button>+9 <button class={[stylesheet.getClass("button"), ...(args.class || [])]}>+10 {args.children}+11 </button> 12 );

Now we are ready to actually start using the Button component. We can replace each button element with the Button component. Also, for the zweo-button we will add the .double-width class.

78 <div>- <button class={[stylesheet.getClass("button")]}>AC</button>- <button class={[stylesheet.getClass("button")]}>C</button>- <button class={[stylesheet.getClass("button")]}>ANS</button>- <button class={[stylesheet.getClass("button")]}>{"&div;"}</button>+79 <Button>AC</Button>+80 <Button>C</Button>+81 <Button>ANS</Button>+82 <Button>{"&div;"}</Button> 83 </div> 84 <div>- <button class={[stylesheet.getClass("button")]}>7</button>- <button class={[stylesheet.getClass("button")]}>8</button>- <button class={[stylesheet.getClass("button")]}>9</button>- <button class={[stylesheet.getClass("button")]}>{"&times;"}</button>+85 <Button>7</Button>+86 <Button>8</Button>+87 <Button>9</Button>+88 <Button>{"&times;"}</Button> 89 </div> 90 <div>- <button class={[stylesheet.getClass("button")]}>4</button>- <button class={[stylesheet.getClass("button")]}>5</button>- <button class={[stylesheet.getClass("button")]}>6</button>- <button class={[stylesheet.getClass("button")]}>-</button>+91 <Button>4</Button>+92 <Button>5</Button>+93 <Button>6</Button>+94 <Button>-</Button> 95 </div> 96 <div>- <button class={[stylesheet.getClass("button")]}>1</button>- <button class={[stylesheet.getClass("button")]}>2</button>- <button class={[stylesheet.getClass("button")]}>3</button>- <button class={[stylesheet.getClass("button")]}>+</button>+97 <Button>1</Button>+98 <Button>2</Button>+99 <Button>3</Button>+100 <Button>+</Button> 101 </div> 102 <div>- <button- class={[- stylesheet.getClass("button"),- stylesheet.getClass("double-width"),- ]}- >- 0- </button>- <button class={[stylesheet.getClass("button")]}>.</button>- <button class={[stylesheet.getClass("button")]}>=</button>+103 <Button class={[stylesheet.getClass("double-width")]}>0</Button>+104 <Button>.</Button>+105 <Button>=</Button> 106 </div>

Depending on the use-case, a component could also be too flexible, i.e. it could be able to do stuff that actually is not necessary. Looking at the Button component, the developer using this component can pass arbitrary classes to the component, but we actually only have one specific use-case where we need an additional class. That is to double the width of the button.

Just to make it clear: What we're about to do is not a general best practice. Depending on the use-case and what your component does, it might also make sense to have the additional flexibility.

For the sake of demonstration, let's now restrict our component to the only use-case it has to handle. We replace the class custom argument with a custom argument called isDoubleWidth, which will accept a boolean value. If the value is true, we will add the .double-width class inside the Button component.

3type ButtonArgs = {- class?: string[];+4 isDoubleWidth?: boolean; 5}; 6 7const Button = gg.component<ButtonArgs>((args) => {- return (- <button class={[stylesheet.getClass("button"), ...(args.class || [])]}>- {args.children}- </button>- );+8 const classes = [stylesheet.getClass("button")];+9 if (args.isDoubleWidth) {+10 classes.push(stylesheet.getClass("double-width"));+11 }+12 return <button class={classes}>{args.children}</button>; 13}); 102 <div>- <Button class={[stylesheet.getClass("double-width")]}>0</Button>+103 <Button isDoubleWidth>0</Button> 104 <Button>.</Button>

There's one thing left we can do. When considering that the Button component could be located anywhere, for example in a completely different file, it makes sense to split our stylesheet into the styles that are only related to the Button component and the rest.

Let's create an additional stylesheet that contains the button styles. We have to use this new stylesheet inside the Button component then, because the existing one won't contain the relevant classes anymore.

2 +3const buttonStyles = gg.stylesheet(`+4 .button {+5 padding: 0.25em 0; +32 .double-width {+33 width: 12rem;+34 }+35`); 41const Button = gg.component<ButtonArgs>((args) => {- const classes = [stylesheet.getClass("button")];+42 const classes = [buttonStyles.getClass("button")]; 43 if (args.isDoubleWidth) {- classes.push(stylesheet.getClass("double-width"));+44 classes.push(buttonStyles.getClass("double-width")); 45 } 65 box-sizing: border-box; 66 }- .button {- padding: 0.25em 0; - .double-width {- width: 12rem;- } 67`);

For simplicity we'll leave all the code for this tutorial in one file, but you could now easily extract the Button component into its own file and import it in any other file you want to use it.


The ground work is done, it's time to start implementing the functionality using client-side-state.


Next → State for the first number