22
createState("Introducing AgileTs. A flexible State-Manager");
One of the most challenging problems to solve, especially in large frontend applications, is managing global States. While there are already several excellent approaches to solving global state management problems, most are tied to a specific workflow. You are often forced to define everything in a single source-of-truth store object, which takes away a lot of flexibility and simplicity. However, have you ever thought about managing your States as global individuals (atoms
) that can be structured as preferred and dynamically bound to any UI-Component for reactivity?
I'm very excited to introduce you to AgileTs. A straightforward, flexible, well-tested State Management Library for Javascript/Typescript applications. AgileTs enables the straightforward creation of individual and independent States (createState('Hello World');
) while providing a powerful toolset focused on developer experience around those States.
The flexibility provided by managing global States as individuals makes AgileTs suitable for both, developers building smaller applications (Style Guide) worrying about writing too much boilerplate code. And for teams creating large applications (Style Guide) trying to create readable, maintainable, and testable code.
Before we dive into a small example, it should be noted that there is no 'perfect' way of managing global States. Each State Management approach has benefits and drawbacks. Depending on the kind of application you are building and your preferred code style, you should weigh which State-Management-Library is best suited for your needs. More on the benefits and drawbacks of AgileTs later.
β
Let's see how AgileTs works with React. To demonstrate its basic capabilities, I will show you how to build a simple application using AgileTs and React. The sample project we'll look at is a small counter that lets us increase a number as we click the 'Update State' button. It may not be fascinating, but it shows all the essential pieces of a React + AgileTs application in action.
Installing AgileTs is as straightforward as installing any other npm packages. First, letβs install it using either npm
or yarn
. To properly work with AgileTs in a React environment, we need to add two different packages to our existing React application.
π If you want to set up a project from scratch, you can also use the official
create-react-app
template for AgileTs.// Javascript npx create-react-app my-app --template agile // Typescript npx create-react-app my-app --template agile-typescript
npm install @agile-ts/core
The core
package contains the state management logic of AgileTs and therefore offers powerful classes such as the State Class
.
npm install @agile-ts/react
The React Integration, on the other hand, is an interface to React and provides useful functions like the useAgile()
Hook to easily bind States to React Components for reactivity.
const MY_FIRST_STATE = createState("Hello World");
After we have successfully installed AgileTs, we can start creating our first independent AgileTs State. All you need to instantiate a State is to call createState()
and specify an initial value.
In our example, we have assigned the initial value 'Hello World' to the State. If you are wondering why we write AgileTs States uppercase. Well, it has a simple advantage. We can easily differentiate between global and local States in our UI-Components (See Step 3οΈβ£).
const RandomComponent = () => {
const myFirstState = useAgile(MY_FIRST_STATE); // <-
return (
<div>
<p>{myFirstState}</p>
</div>
);
}
Here (// <-
) we bind our just created State to the React Component ('RandomComponent') using the useAgile()
Hook. This binding ensures that the Component re-renders whenever the State value
mutates. The useAgile()
Hook returns the current value
of the State. So in our case, something like 'Hello World'.
MY_FIRST_STATE.set(`Hello World ${++helloWorldCount}`);
To bring some life into our small application, we update the State value
with the help of the State's .set()
function on each 'Update State' button press. Thereby we increase the external set helloWorldCount
in ascending order.
Here we see the whole counter-example in one piece.
// 2οΈβ£ Create State with the initial value "Hello World"
const MY_FIRST_STATE = App.createState("Hello World");
let helloWorldCount = 0;
const RandomComponent = () => {
// 3οΈβ£ Bind initialized State to the 'RandomComponent' for reactivity
const myFirstState = useAgile(MY_FIRST_STATE);
return (
<div>
<p>{myFirstState}</p>
<button
onClick={() => {
// 4οΈβ£ Update State value on Button press
MY_FIRST_STATE.set(`Hello World ${++helloWorldCount}`);
}}
>
Update State
</button>
</div>
);
}
If you are eager to learn more about AgileTs, take a look at our documentation.
β
Unfortunately, this blog post can't cover how to use AgileTs in other frontend frameworks than React, as that would be beyond the scope. However, the core principle of AgileTs is in each UI-Framework the same. The only part that might differ is how to bind States to UI-Components for reactivity (Step 3οΈβ£).
Here are code sandboxes for each already supported UI-Framework with the same counter-example
as in the React example section above:
β
Yes, AgileTs follows the same pattern as atomic
State Management Libraries like Recoil. States in AgileTs are created individually and lay above the UI-Layer, while they can be dynamically bound to any UI-Component (for example via Hooks).
In AgileTs, States are not called atoms, but rather individual or perhaps singleton States. However, the main difference to Recoil is that AgileTs doesn't depend on React, can be used outside the React-Tree, is more feature-rich and beginner-friendly.
β
After our little excursion on how AgileTs works in React, we already understand its basic API and functionality. So let's talk about what exactly makes AgileTs so special and some benefits of using it.
As you may have noticed in the React example above,
the API of AgileTs is fairly easy to understand and self-explaining. This is no coincidence; AgileTs is designed to write minimalistic, boilerplate-free code that captures your intent.
// Update State value to 'hi'
MY_STATE.set('hi');
// Undo latest State value change
MY_STATE.undo();
// Check if the State value is equal to '{hello: "jeff"}'
MY_STATE.is({hello: "jeff"});
// Reset State to its intial value
MY_STATE.reset();
// Preserves the State `value` in the corresponding external Storage
MY_STATE.persist();
// Update State value in 200ms intervals
MY_STATE.interval((value) => value++, 200);
In AgileTs, States are created detached from each other and have an independent existence. Think of AgileTs States as global variables that can be structured as preferred and dynamically bound to any UI-Component. AgileTs States are partly like UI-Components since UI-Components are also just global variables embedded in other UI-Components.
The given flexibility has a lot of advantages. However, the capability to initialize States everywhere might lead to an unstructured and not transparent application, which quickly ends in a mess. To help you not to end up there, we have created some Style Guides to give you some inspiration on how to structure a frontend application using AgileTs.
Based on the functionality of the basic AgileTs State, we have created further helpful classes, such as:
π¨βπ« Computed State
Computed States are a powerful concept that lets us build dynamic data depending on other data. To avoid unnecessary recomputations, the Computed Class caches the computed value and recomputes it only when an actual dependency has changed.
const INTRODUCTION= App.createComputed(() => {
return `Hello I am '${MY_NAME.vale}'.`;
});
A Computed magically tracks used dependencies (such as States) and automatically recomputes when one of its dependencies updates. In the above code snippet, it would, for example, recompute when the current value of MY_NAME
changes from 'jeff' to 'hans'.
INTRODUCTION.value; // Returns "Hello I am 'jeff'."
MY_NAME.set('hans');
INTRODUCTION.value; // Returns "Hello I am 'hans'."
π¨βπ©βπ§ Collection State
Collection States come in handy when managing a set of information, such as a list of todos or users. A Collection is specially designed for arrays of data objects
following the same pattern. Each of these data objects requires a unique item key
to be correctly identified later. Think of a Collection like a database table that stores a data object once keyed by an id (item key
).
const JOKES = App.createCollection();
In the above example, we've created a Collection that stores a list of Jokes. However, a joke list without jokes isn't funny.
So let's add a funny joke to our newly created Joke Collection.
JOKES.collect({
id: 1,
joke: "Why do Java programmers have to wear glasses?\n
Because they don't C#"
}, ['programming']);
The joke we've just added belongs to the category 'Programming'. Therefore we categorize it to the programming
Group. Groups allow us to easily cluster together data from a Collection as an array of item keys.
JOKES.getGroup('chucknorris').value; // Returns Chuck Norris Jokes
JOKES.getGroup('programming').value; // Returns Programming Jokes
JOKES.getDefaultGroup().value; // Returns All Jokes
AgileTs assures performance optimization by batching re-render jobs and only re-rendering the UI-Components when an actual bound State mutates. You can go even further by only binding particular properties of a State value to the UI-Component or using the inbuilt proxy functionality.
// Component re-renders only when 'user.name' mutates
const name = useSelector(MY_USER, (value) => value.name);
console.log(name); // Returns 'jeff'
// Component re-renders only when 'user.age' mutates
const user = useProxy(MY_USER);
console.log(user.age); // Returns '8'
AgileTs has no advanced dev tools
yet.
However, you can bind your States to the globalThis
and easily access them in the browser console.
const MY_STATE = createState('jeff');
const MY_COLLECTION = createCollection();
globalBind('__core__', {
MY_STATE,
MY_COLLECTION
});
This allows you to preview and edit your global bound States at runtime. For example, the core
of the AgileTs documentation is globally bound for better debugging. Note that you should avoid attaching your application States to the globalThis
in production because then third parties can easily interfere in your internal application logic. Since the AgileTs documentation has no vulnerable logic under the hood, the core
is also accessible in production. Thus you can play around with the AgileTs documentation core
and, for example, update the NPM_DOWNLOADS
State or update the astronaut color.
__core__.stats.NPM_DOWNLOADS.set(999999);
β
Like any other great global State Manager, also AgileTs comes with some drawbacks that we should talk about. We are working hard to reduce and get rid of these. If you have any further concerns about using AgileTs, let me know in the comments. Then I can list them here and maybe even counteract them π. Thanks for your support.
Most State-Manager are pretty lightweight, but not this one. AgileTs has a minified size of 58.3kB (tree shaken 18kB) and is pretty heavy compared to its fellows. However, it offers a 100% type safety, a predictable runtime, an API focusing on developer experience, and much more in return. The large bundle size doesn't mean that AgileTs slows down your application noticeably. Convince yourself with the below listed AgileTs stress tests:
We have also created some benchmarks that compare different State Management approaches in terms of performance.
AgileTs hasn't been officially released until now (July 2021)
and I've not managed to build a community around the library yet. This was mainly because I thought AgileTs was not yet good enough to be shown to anyone. But well, among many other things I've learned while developing AgileTs, I've also learned that it's never too early to ask for feedback. π
If you want to become a part of the AgileTs community, don't hesitate to join our Community Discord. There you can ask anything related to AgileTs or programming in general and tell us what you think about AgileTs or what we can do better.
It may be strange, but if I (the only contributor) get hit by a tree or something and die, AgileTs will no longer have a maintainer. I've tried to create a as contributor-friendly codebase as possible. But still, it doesn't matter how many people are able to understand the code and fix the issues that might occur if no one can merge/release those changes.
β
In conclusion, AgileTs provides a simple yet powerful API that focuses on developer experience and meets the need for small and large applications by being scalable without writing any boilerplate code. Therefore, AgileTs looks to be an excellent candidate to consider for State Management. Although it is not lightweight, it tries to optimize the performance of our applications wherever it can by batching re-renders and offering proxy-based functionalities like the useProxy()
hook.
At last, thanks for taking the time to read this article. I would appreciate hearing what you think about AgileTs in the comments. In case you have any further questions, don't hesitate to join our Community Discord or ask on our subreddit. We are eager to help. And if you like the concept of AgileTs or/and want to support us, give us a βοΈ (star) on Github and share it with your friends. Thanks for your support π
Cheers π
- Github: https://github.com/agile-ts/agile
- Website: https://agile-ts.org/
- Discord: https://discord.gg/T9GzreAwPH
- Twitter: https://twitter.com/AgileTypescript
- Reddit: https://www.reddit.com/r/AgileTs/
22