Routing and Easy Form Fields on FRETS 0.3

Made a few changes to my little web frontend library, FRETS. First of all I updated the online API documentation to clean up old pieces of code and document more funcitons in the main module.

First, one big bug was fixed. We were getting double rendering of the same state change because I wasn’t checking the cache properly. The new cache checking code should prevent double renders and still allow rendering to happen when an async function (like a fetch) calls FRETS.render(newProps).

Here’s an example of the right way to call async functions from an event listener (action).

1
2
3
4
5
6
7
8
9
10
11
F.actions.loadUser = F.registerAction((e: Event, props: AppProps) => {
fetch("https://jsonplaceholder.typicode.com/users")
.then(response => response.json())
.then(json => {
const user = json[(Math.random() * 10).toFixed()];
console.log("recieved fetch");
props.username = user.username,
F.render(props);
});
return props;
})

Fields Registry

On previous apps, for every single field that a user could change I would have to do several steps.

  • Add a property to the main modelProps class
  • Add an empty action function signature to the main actions class.
  • Add a concrete implementation of the action event handler. Usually some variation of this code
1
props.someProperty = (e.currentTarget as HTMLInputElement).value

Writing the same boilerplate is repetitive. I thought about how we as developers want to add functionality to our apps in flexible and composable way. That’s what components in Vue and React are all about. With those libraries you are building an object to fit some sort of component structure so that you can register properties inside of child components that all get rolled up into the master component. But, I want to keep FRETS free of configuration objects and string key conventions that need to be memorized.

So, I added registration methods on FRETS so that you can call a registerField() method from inside of your UI rendering methods. The entire frets App object is expected to be injected into your render methods now. That way you can still extract model props and actions from the app instance for rendering the UI, and now you can add fields to the main registry — and FRETS takes care of the repetetive event handler creation.

1
2
3
4
5
const countField = app.registerField<string>("count", "1");
return $.input.m1.h({
onblur: countField.handler,
value: countField.value,
}),

When you call registerField you get back an object with the handler and the current value, and any validation errors that have been attached to it during the validation step.

This makes it much quicker to add form inputs that are tied into your app state without having to know and declare every single model property up front. Now you can inject functionality to your app from within the render methods themselves increasing decoupling of the main app code and enabling functionality and features to be declared from within js chunks that are loaded asynchronously only when they are needed.

Routing

When I build single page apps I often end up needing to do some sort of routing to different screens based on the URL. I took it on as a challenge to enable this feature in a functional way, not using complicated configuration objects like Vue-router does. So, when you want to register a route in FRETS - what you are really doing is registering a string for matching the current URL value, and a function that will update your app state in some way when the current url matches your decalared pattern. It can be as simple as switching a value from “screen: 0” to “screen: 1”.

1
2
3
4
F.registerRoute(RouteKeys.About, "/about", (name, params, props) => {
props.activeScreen = SampleScreens.About;
return props;
});

Then when it comes time to navigate to a screen programmatically you will call a method on the app instance from within your event handler action.

1
2
3
4
5
6
F.actions.navAbout = F.registerAction((e: Event, props: AppProps): AppProps => {
F.navToRoute(RouteKeys.About);
props.activeScreen = SampleScreens.About;
return props;
});

Performance

The overall library size is a bit larger (15.3kb minified and Gzipped instead of 10.6kb) because I added a second dependency: the path matching library “path-parser”. In addition, the necessary features from Maquette are still included in compiled library.

I still recommend writing your UI rendering methods using atomic CSS library like BassCSS or Tachyons compiled to JS functions using frets-styles-generator. This will make writing UI code much more pleasant but adds a few more KB of javascript to your final bundle.

The updated example app frets-starter builds a final javascript bundle of 36KB minified and gzip. Which I think is very respectable. View that demo app.

Other Changes

I increased unit test coverage to around 75% now.

To support the field registry your actions and props classes should now inherit PropsWithFields and ActionsWithFields

1
2
3
class MyProps extends PropsWithFields {}
class MyActions extends ActionsWithFields {}

As mentioned before, the new UiRendering method that’s passed into FRETS.registerView() should accept the entire frets object, it’s signature should be:

1
renderFn: (app: FRETS<MyProps, MyActions>) => VNode