tardigrade-store
v1.1.24
Published
A simple state management library
Downloads
14
Readme
Tardigrade
Tardigrade is free lightweight zero-dependency javascript library for managing state and data with strict typing, supporting dynamic properties, property-specific, global and local listeners, and automatic cleanup for safe and flexible state control
Stable version soon!
I am going to drop first stable build soon
Attention!
Tardigrade still is under constructions
There is import issue wit some builders (for instance parcel.js), but you can tackle it with
import { createTardigrade } from "tardigrade-store/dist/tardigrade.store.es";
I am still working to rollup it properly
Why this library was created
This library was created to offer a simplified approach to state management with enhanced type safety, addressing some of the complexities and boilerplate often found in solutions like Redux. With this library, the goal was to create a lightweight state management tool that eliminates unnecessary configuration and ensures strict type control, while still being flexible enough to handle dynamic state changes and listeners
Change log
There is changelog.md
file, and you can easily check all changes that was added
Advantages of Tardigrade
I've designed Tardigrade to offer a flexible, safe, and intuitive way to manage state in applications. Here's why this approach stands out:
Ease of use
Minimal boilerplate code and a fairly straightforward API. Tardigrade provides a structured way to manage state, but with fewer steps. Tardigrade reduces the need for writing actions, reducers, and dispatchers, allowing developers to focus more on business logic.
Full Dynamic State Control
Tardigrade allows you to dynamically add and remove properties, clone and merge stores and others, making it ideal for applications that need to modify their state structure on the fly. This flexibility is crucial for dynamic applications where data models evolve over time.
Immutability
Tardigrade works with immutable data even if the provided data was originally mutable. It ensures that all state transformations maintain immutability, preventing unintended side effects and making state changes more predictable and reliable
Small size
Tardigrade had a small size
Ease of async
With Tardigrade's resolvers you can easily store async or dynamic derived data
Basic usage
Creating an Instance
To start using the library, first create an instance of it:
import { createTardigrade } from "tardigrade";
const tardigrade = createTardigrade();
Adding Properties
You can dynamically add properties to the state using the addProp
method. Each property has a type that is locked once it is added.
tardigrade.addProp("counter", 0); // Adds a property 'counter' with an initial value of 0
tardigrade.addProp("username", "guest"); // Adds a property 'username' with an initial value of 'guest'
Handle Properties
To update a property, use the setProp
method. The new value must match the type of the property that was originally set:
tardigrade.setProp("counter", 5); // Updates 'counter' to 5
tardigrade.setProp("username", "admin"); // Updates 'username' to 'admin'
You can also set a property to null without changing its type:
tardigrade.setProp("username", null); // Sets 'username' to null, but its type remains string
And if you need to get prop you can tackle it with prop
method:
tardigrade.setProp("counter", tardigrade.prop("counter") + 1);
It is also possible to get all the props as simple object with getter props
console.log(tardigrade.props); // will print all the props
To check was prop added use hasProp
method
tardigrade.addProp("role", "customer");
console.log(tardigrade.hasProp("role")); // return true
Listening to Property Changes
There are two types of listeners you can add:
- Property-Specific Listeners: These listen to changes on a specific property
- Global Listeners: These listen to changes across all properties
Property-Specific Listener
To listen for changes on a specific property, use the addPropListener
method:
const propListener = (value) => console.log("Counter changed to", value);
tardigrade.addPropListener("counter", propListener);
Whenever the counter property is updated, the propListener
will be called with the new value
Global Listener
To listen for any change across all properties, use the addListener method:
const globalListener = (name, value, props) => {
console.log('Global update');
console.log(`Property ${name} changed to, ${value}`);
console.log('All props', props);
};
tardigrade.addListener(globalListener);
This global listener is triggered whenever any property in the library is updated
Removing Listeners
Listeners can be removed to avoid memory leaks or when they are no longer needed
Remove Property-Specific Listener
To remove a specific listener for a property, use the removePropListener
method:
tardigrade.removePropListener("counter", propListener);
Remove All Property-Specific Listeners
If you need to remove all property-specific listeners at once, you can call removeAllPropListeners
:
tardigrade.removeAllPropListeners("counter");
Remove Global Listener
Similarly, you can remove global listeners with removeListener
:
tardigrade.removeListener(globalListener);
Remove All Global Listeners
If you need to remove all global listeners at once, you can call removeAllListeners
:
tardigrade.removeAllListeners();
Removing Properties
You can dynamically remove properties using the removeProp
method. This will also automatically remove all listeners attached to the property
tardigrade.removeProp("username"); // Removes 'username' property and its listeners
Once a property is removed, any attempt to update it will result in an error:
tardigrade.setProp("username", "newUser"); // Throws an error since 'username' was removed
You can also remove all the props by single hit with removeAllProps
. It is also remove all props listeners
tardigrade.removeAllProps();
Import props
You can import props of another Tardigrade store into your with merge
method. This will automatically replace props from target store to that you need
const altStore = createTardigrade();
altStore.addProps("timestamp", 0);
tardigrade.importProps("altStore"); // Replaced "timestamp" prop into our base store
importProps
method replace only props, without prop's listener handlers
There are two ways to import props: without override or with
tardigrade.importProps("altStore", true); // Will override existing props of this store after import
tardigrade.importProps("altStore"); // Won't override existing props of this store after import
Merge stores
You can merge one store into another. Merging replace not only props but all the listener handlers and make source object stop working
const altStore = createTardigrade();
altStore.addProps("timestamp", 0);
altStore.addPropListener("timestamp", (value) => console.log(value));
tardigrade.merge(altStore); // Replaced "timestamp" prop and all the prop listener handlers
consolel.log(altStore.prop("timestamp")); // would return "null" cause altStore was killed after merging
As importProps
this method also has two ways to be executed
tardigrade.merge(altStore, true); // Will override existing props and listener handlers of this store after import
tardigrade.merge(altStore); // Won't override existing props and listener handlers of this store after import
Core kills merged store to keep single truth origin, but deactivated store after merging get link to mergeAgent
- store which was merged and killed it.
Also, it can be null if store is active
const currentStore = altStore.mergeAgent || altStore;
Full Example
Here’s how you can combine everything:
import { createTardigrade } from "tardigrade";
const tardigrade = createTardigrade();
// Add properties
tardigrade.addProp("counter", 0);
tardigrade.addProp("username", "guest");
// Add listeners
const propListener = (value) => console.log("Counter changed to", value);
tardigrade.addPropListener("counter", propListener);
const globalListener = (name, value, props) => {
console.log(`Global: ${name} changed to, ${value}`);
};
tardigrade.addListener(globalListener);
// Update properties
tardigrade.setProp("counter", 5);
tardigrade.setProp("username", "admin");
// Remove listeners
tardigrade.removePropListener("counter", propListener);
tardigrade.removeListener(globalListener);
// Remove a property
tardigrade.removeProp("username");
Getter isAlive(): boolean
You can check activity of your store with isAlive
getter
console.log(tardigrade.isAlive); // will print store state
Reset
In any moment you can reset your alive store to empty state. All props, resolvers, global and local listeners will be removed
tardigrade.reset();
Resolvers
Resolver - is a function which can be pass into store and be called in certain moment to bring some value. Application can follow resolvers as props to control state
Simple resolver usage
import { createTardigrade } from "tardigrade";
const tardigrade = createTardigrade();
tardigrade.addResolver("random", () => Math.random());
tardigrade.addResolverListener("random", (value) => {
console.log(`Resolver was called and bring ${value}`);
});
tardigrade.callResolver("random"); // as result it will call resolver listener handler above
Income resolver handler can use single argument - all the current props object
import { createTardigrade } from "tardigrade";
const tardigrade = createTardigrade();
tardigrade.addProp("money", 1e5);
tardigrade.addResolver("multiplyMoneyRandomly", ({ money }) => Math.random() * money);
tardigrade.addResolverListener("multiplyMoneyRandomly", (value) => {
console.log(`Resolver was called and bring ${value}`);
});
tardigrade.callResolver("multiplyMoneyRandomly");
Resolver also migrate as well by merging and can be imported
You can also remove single resolver as well
tardigrade.removeResolver("multiplyMoneyRandomly");
Or you can drop all the resolvers by single hit
tardigrade.removeAllResolvers();
To check was resolver added use hasResolver
method
tardigrade.addResolver("getUsers", async () => { /* ...some stuff */ });
console.log(tardigrade.hasProp("getUsers")); // return true
Usage with async
Also, you can use it with async function to do stuff like that
import { createTardigrade } from "./";
(async () => {
const tardigrade = createTardigrade();
const resolverKeys = {
fetchSomeSpecial: "fetchSomeSpecial",
};
tardigrade.addResolver(resolverKeys.fetchSomeSpecial, async () => {
try {
const response = await fetch("https://jsonplaceholder.org/posts");
return response.json();
} catch (error) {
console.log('Fetch error', error);
return null;
}
});
tardigrade.addResolverListener(resolverKeys.fetchSomeSpecial, (fetchedValue) => {
console.log("Fetched value is", fetchedValue);
});
await tardigrade.callResolver(resolverKeys.fetchSomeSpecial);
})();
createTardigrade
variants
At the beginning we learn how create basic instance of store. But you are able to use some options to instancing
Initial props and resolvers
Pass json-friendly object as first argument. All what is function would become resolver and all other what is has another type would become prop
import { createTardigrade } from "./";
(async () => {
const propKeys = {
counter: "counter",
};
const resolverKeys = {
fetchSomeSpecial: "fetchSomeSpecial",
};
const tardigrade = createTardigrade({
[propKeys.counter]: 0,
[resolverKeys.fetchSomeSpecial]: async () => {
try {
const response = await fetch("https://jsonplaceholder.org/posts");
return response.json();
} catch (error) {
console.log('Fetch error', error);
return null;
}
},
});
tardigrade.addPropListener(propKeys.counter, (counterValue) => {
console.log(`Counter equals ${counterValue}`);
});
tardigrade.addResolverListener(resolverKeys.fetchSomeSpecial, (fetchedValue) => {
console.log("Fetched value is", fetchedValue);
});
await tardigrade.callResolver(resolverKeys.fetchSomeSpecial);
for (let i = 0; i < 10; i++) {
tardigrade.setProp(propKeys.counter, tardigrade.prop(propKeys.counter) + 1);
}
})();
Store initial options
With second argument you can pass some initialised options for store, for instance:
import { createTardigrade } from "./";
(async () => {
const tardigrade = createTardigrade({
"counter": 0,
}, {
emitErrors: true
});
tardigrade.setProp('text', "Lorem ipsum"); // bring real error
})();
There are several options:
emitErrors: boolean
- this options control how store react onto errors. If this prop equals true then store is going to crash after any incorrect usage.
If false - you get only error-messages in console without real errors. By default, this initial prop equals false
name: string | number | symbol
- it is basically name of your store. It can be useful in case you want to get some store from array.
By default, name equals random uuid
strictObjectsInterfaces
- this parameter specifies how strictly to enforce type-checking on prop that has object
type.
If the parameter equals true
, it's not possible to assign an object to a prop if its interface differs from the expected one
For instance
const propNames = {
user: "user",
};
const store = createTardigrade({
[propNames.user]: {
name: "Alise", // will get string type
age: 100, // will get number type
data: null, // will get any type, you can write any type here later
},
},
{
strictObjectsInterfaces: true
});
store.addPropListener(propNames.user, value => console.log(value));
store.setProp(propNames.user, {
name: "Bob",
age: 200,
data: Symbol("Bob")
});
store.setProp(propNames.user, {
name: "Frank",
age: "200",
}); // Bring error cause interface isn't the same
Links
Github: fimshagal/tardigrade
E-mail: [email protected]
Support
If you find this package useful, consider supporting my work via PayPal: