gg logo

gg

Documentation

Changelog

Blog

State

BasicsCopy link to section

While gg focuses on building static websites, it's very common to have some interactive elements on otherwise mostly static pages. That is why gg also provides simple APIs for managing client-side state.

State in gg works quite differently compared to other libraries and frameworks like React or Vue. The former, for example, re-computes the whole component tree of the page in the client, then compares it to the current DOM and updates the elements that changed. This concept is known as Virtual DOM.

gg has a different approach. All elements that depend on state are already marked at build-time, later at run-time, those elements are precisely updated when the related state changes. This is not just much faster than a virtual DOM, it also results in way less JavaScript that needs to be shipped to your users.

Another key difference that shall be noted is that state in gg is not bound to any component. You can freely create state instances and pass them around your application, using them in as many pages, files, or components as you like.

When working with state in gg, always keep in mind that component functions are never run on the client. Using Web APIs like the Location-API inside component functions will not work.

Creating stateCopy link to section

Client-side state can be created using the gg.state method. The first argument is interpreted as the initial value of the state.

1const text = gg.state("world");

Using state in JSX is as easy as adding a JSX expression containing the state variable.

1const text = gg.state("world");+2const markup = <p>Hello {text}</p>;

When you create state, TypeScript will infer the type of the state based on the initial value. This might not always be the type you want. In that case, it is recommended to pass an explicitly typed variable as the initial value.

-const text = gg.state("world");+1const initialValue: "world" | "gg" = "world";+2const text = gg.state(initialValue); 3const markup = <p>Hello {text}</p>;

Note that the text variable is not a plain string. It actually is an instance of a class called State that gg uses internally to figure out, which parts of your HTML are depending on state. In particular, you must not modify this variable in any kind of way!

1const text = gg.state("world"); 2// Do NOT do this! 3const markup = <p>Hello {text + "!"}</p>; 4// This also will not work! 5const markup2 = <p>Hello {text.slice(0, 1)}</p>;

SelectorsCopy link to section

When creating state, you can pass a set of so-called selectors, which are basically functions that transform the value of the state into a boolean, a number, a string, an array of strings, or a map with boolean values. (In fact, these are all the types of values that are valid attribute values, except for functions.)

This is especially handy if the value of the state itself is not one of those types, for example, if you base your state on an object with a more complex structure.

To add selectors to a state, you can pass an object as the second argument to gg.state. The properties of this object are the names of the selectors, and the values are functions that will be called with the value of the state.

1const person = gg.state( 2 { firstName: "Thomas", lastName: "Heyenbrock", age: 26 }, 3 { 4 getName: (value) => value.firstName + " " + value.lastName, 5 getAge: (value) => value.age, 6 }, 7);

The selectors created based on those functions are accessible under the selectors property of the state variable. As with the state itself, you can just use those selectors in JSX expressions.

7);+8const markup = (+9 <p>+10 {person.selectors.getName} is {person.selectors.getAge} years old+11 </p>+12);

As with state variables, you also must not the selectors in any kind of way.

Setting the state valueCopy link to section

To update the value of any state you can use the gg.setState method. As the first argument you need to specify the state that you want to update. The second argument has to be a function that will return the new state value. It will be called with the current state value as the first argument and with the event that triggered the state update as the second argument.

The gg.setState method returns a special instance that you can pass as a value to any event handler attribute.

1const count = gg.state(0); 2const markup = ( 3 <button 4 onclick={gg.setState(count, (currentCount, event) => { 5 console.log("This was triggered by a " + event.type + "-event."); 6 return currentCount + 1; 7 })} 8 > 9 The count is {count} 10 </button> 11);

Note that the same restriction holds for a function that you pass to setState as it did for plain functions passed to event handler attributes. gg does not parse the function, so it does not know about any dependencies to variables or other functions defined outside the scope of the function. That means you can't use anything that is not global or not declared within the scope of the function.

Interdependent stateCopy link to section

When updating state, it's a common use-case to base the new value of one state not just on the current value of the state itself, but also on the value of another state. gg allows you to define such dependencies when creating set-state-handlers.

You can pass an array of state variables as the third argument to gg.setState. The function that returns the new value of the state will then be called with a third argument. This will also be an array, this time containing the current values of the states in the same order you passed the variables to gg.setState.

1const a = gg.state(1); 2const b = gg.state(2); 3const sum = gg.state(0); 4const markup = ( 5 <button 6 onclick={gg.setState( 7 sum, 8 (currentSum, event, [firstNumber, secondNumber]) => { 9 return firstNumber + secondNumber 10 }, 11 [a, b], 12 )} 13 > 14 Compute the sum of {a} and {b} 15 </button> 16);

Updating multiple states at onceCopy link to section

It's easy to update multiple states at once, you can just pass an array containing multiple set-state-handlers as attribute value to an event handler attribute like onclick.

1const a = gg.state(0); 2const b = gg.state(1); 3const markup = ( 4 <button 5 onclick={[ 6 gg.setState(a, (currentValue) => currentValue + 1), 7 gg.setState(b, (currentValue) => currentValue + 1), 8 ]} 9 > 10 Click me 11 </button> 12);

When there also exist dependencies between multiple states that are updated at once, keep in mind that the event handlers are executed in the order that you pass them in the value of the event handler attribute. As an example, when the second handler is executed, the state used in the first handler will already be updated.

1const a = gg.state(0); 2const b = gg.state(1); 3const markup = ( 4 <button 5 onclick={[ 6 gg.setState( 7 a, 8 (currentValue, event, [bValue]) => { 9 // This function executes first, 10 // thus `bValue` is still `1` 11 return currentValue + bValue; 12 }, 13 [b], 14 ), 15 gg.setState( 16 b, 17 (currentValue, event, [aValue]) => { 18 // This function executes second, 19 // thus `aValue` is now already `0 + 1 = 1` 20 return currentValue + aValue; 21 }, 22 [a], 23 ), 24 ]} 25 > 26 Click me 27 </button> 28);

ListsCopy link to section

Another common use-case is showing lists whose length and items can change client-side. Since gg does not re-run the component function on the client, you can't just map over a state with an array as value like you would in React.

Instead, gg provides a separate method gg.unstable_list which you can use to create dynamic lists.

Be warned, this method is still considered unstable. There are known bugs when trying to create nested lists, i.e. lists inside of lists. However, only using flat lists should already be quite stable. Changes in the exact API are likely to happen in the future.

A dynamic list needs to be based on a state instance, where the state value is an array of objects. Every object in this array has to contain an id property with a string value that's unique for the given array. This is required so that gg knows when updating the list state which element in the DOM refers to which object in the array.

The gg.unstable_list method accepts two arguments. First the state that it's based on, second an object containing selectors. Unlike selectors for states, these functions will be called with one list item only, not with the complete state value.

1const items = gg.state([ 2 { id: "0", beverage: "Beer" }, 3 { id: "1", beverage: "Coffee" }, 4 { id: "2", beverage: "Tea" }, 5]); 6const beverages = gg.unstable_list(items, { 7 getBeverage: (listItem) => listItem.beverage, 8});

The return value of gg.unstable_list is again not a plain array, but an object that should resemble one. In particular, it provides a map function that behaves like Array.prototype.map. That means we can pass it a callback function that returns the markup for a given list item. This function will be called with the selectors that we defined when calling gg.unstable_list.

6const beverages = gg.unstable_list(items, { 7 getBeverage: (listItem) => listItem.beverage, 8});+9const markup = (+10 <ul>+11 {beverages.map((selectors) => (+12 <li>{selectors.getBeverage}</li>+13 ))}+14 </ul>+15);

Each list item can also create and update its own state.

1const items = gg.state([ 2 { id: "0", beverage: "Beer", price: 3.5 }, 3 { id: "1", beverage: "Coffee", price: 2 }, 4 { id: "2", beverage: "Tea", price: 3 }, 5]); 6const beverages = gg.unstable_list(items, { 7 getBeverage: (listItem) => listItem.beverage, 8 getPrice: (listItem) => listItem.price, 9}); 10const markup = ( 11 <ul> 12 {beverages.map((selectors) => { 13 const showPrice = gg.state(false); 14 return ( 15 <li> 16 {selectors.getBeverage}{" "} 17 <button onclick={gg.setState(showPrice, (value) => !value)}> 18 toggle price visibility 19 </button> 20 <span hidden={showPrice}>{selectors.getPrice}</span> 21 </li> 22 ); 23 })} 24 </ul> 25);

Now that you know all about developing gg applications, it's time to talk about getting the app into production.


Next → Build and deploy