All Projects → andrewcourtice → Harlem

andrewcourtice / Harlem

Licence: mit
Simple, unopinionated, lightweight and extensible state management for Vue 3

Programming Languages

javascript
184084 projects - #8 most used programming language
typescript
32286 projects

Projects that are alternatives of or similar to Harlem

When Ts
When: recombinant design pattern for state machines based on gene expression with a temporal model
Stars: ✭ 112 (-18.25%)
Mutual labels:  state-management
Jquery Ajaxy
jQuery Ajaxy aims at solving complicated AJAX Paradigms by providing you with a easy managed solution to bind into page state (URL Hash) changes, AJAX form submits, and support AJAX links
Stars: ✭ 125 (-8.76%)
Mutual labels:  state-management
React Atom
A simple way manage state in React, inspired by Clojure(Script) and reagent.cljs
Stars: ✭ 133 (-2.92%)
Mutual labels:  state-management
Bloc.js
A predictable state management library that helps implement the BLoC design pattern in JavaScript
Stars: ✭ 111 (-18.98%)
Mutual labels:  state-management
Formula
A functional reactive framework for managing state and side effects based on RxJava.
Stars: ✭ 118 (-13.87%)
Mutual labels:  state-management
Stateshot
💾 Non-aggressive history state management with structure sharing.
Stars: ✭ 128 (-6.57%)
Mutual labels:  state-management
Reworm
🍫 the simplest way to manage state
Stars: ✭ 1,467 (+970.8%)
Mutual labels:  state-management
Freactal
Clean and robust state management for React and React-like libs.
Stars: ✭ 1,676 (+1123.36%)
Mutual labels:  state-management
Desktop Profiles
An innovative desktop manager for macOS
Stars: ✭ 122 (-10.95%)
Mutual labels:  state-management
Multiple Counters Flutter
Flutter State Management [ setState ❖ StreamBuilder ❖ scoped_model ❖ redux ]
Stars: ✭ 131 (-4.38%)
Mutual labels:  state-management
Bistate
A state management library for React combined immutable, mutable and reactive mode
Stars: ✭ 113 (-17.52%)
Mutual labels:  state-management
React Workshop
⚒ 🚧 This is a workshop for learning how to build React Applications
Stars: ✭ 114 (-16.79%)
Mutual labels:  state-management
Ayanami
🍭 A better way to react with state
Stars: ✭ 129 (-5.84%)
Mutual labels:  state-management
Zproc
Process on steroids
Stars: ✭ 112 (-18.25%)
Mutual labels:  state-management
Pure Store
A tiny immutable store with type safety.
Stars: ✭ 133 (-2.92%)
Mutual labels:  state-management
Movie app state management flutter
Flutter State Management: Movie App with Provider, Riverpod, flutter_bloc
Stars: ✭ 112 (-18.25%)
Mutual labels:  state-management
Vue State Store
📮 VSS (Vue State Store) - Vue State Management (with Publish & Subscribe pattern)
Stars: ✭ 128 (-6.57%)
Mutual labels:  state-management
Ngx Model
Angular Model. Simple state management with minimalistic API, one way data flow, multiple model support and immutable data exposed as RxJS Observable.
Stars: ✭ 137 (+0%)
Mutual labels:  state-management
Reactive State
Redux-clone build with strict typing and RxJS down to its core. Wrist-friendly, no boilerplate or endless switch statements
Stars: ✭ 135 (-1.46%)
Mutual labels:  state-management
Swiftdux
Predictable state management for SwiftUI applications.
Stars: ✭ 130 (-5.11%)
Mutual labels:  state-management

Harlem

Harlem

npm

Simple, unopinionated, lightweight and extensible state management for Vue 3. Take a look at the demo to see it in action or play around with it in CodeSandbox.

Features

Simple

Harlem has a simple functional API for creating, reading and mutating state. At it's heart, Harlem just uses Vue reactive objects and computeds which means if you know how to use Vue, you'll know how to use Harlem.

Unopinionated

Harlem doesn't impose any standards or conventions on your codebase. Because of it's simple functional API you can structure your code anyway you want and Harlem will just work.

Immutable

All state provided from a Harlem store is immutable by default. The only write access to state is through mutations. This ensures all updates to your store are tracable, thereby reducing the amount of bugs produced by code unpredictably mutating state.

Lightweight

Harlem weighs in at around 1KB (minified & gzipped) which makes it the perfect solution for codebases of all sizes. Harlem is also designed to be tree-shakable - unused stores, getters, or mutations will be removed from your code at build time (provided you are using a build tool that supports tree-shaking).

It's also worth noting that Harlem has zero dependencies (apart from Vue obviously).

Extensible

Harlem uses a plugin architecture so you can extend it any way you want. Some of the official plugins include Vue devtools integration, local/session storage sync, and transactions for rolling back multiple mutations when write errors occur.

Great DX

Harlem has a great developer experience. It's built using TypeScript so all of your state, getters, and mutations are strongly typed. Harlem also has devtools integration so you can explore your stores and see mutation events on the timeline in realtime.

Getting started

Getting started is simple:

  1. Install @harlem/core and any plugins you wish to include (I recommend installing @harlem/plugin-devtools during development):
npm install @harlem/core

Or if you're using Yarn:

yarn add @harlem/core
  1. Register the Harlem plugin with your Vue app instance:
import App from './app.vue';
import Harlem from '@harlem/core';

createApp(App)
    .use(Harlem)
    .mount('#app');
  1. Create your store and write any getters/mutations:
import {
    createStore
} from '@harlem/core';

const STATE = {
    firstName: 'John',
    lastName: 'Smith'
};

const {
    getter,
    mutation,
    ...store
} = createStore('user', STATE);

export const state = store.state;

export const fullName = getter('fullname', state => `${state.firstName} ${state.lastName}`);

export const setFirstName = mutation('setFirstName', (state, payload) => {
    state.firstName = payload || '';
});

export const setLastName = mutation('setLastName', (state, payload) => {
    state.lastName = payload || '';
});
  1. Use your store in your app:
<template>
    <div class="app">
        <h1>Hello {{ fullName }}</h1>
        <input type="text" v-model="firstName" placeholder="First name">
        <input type="text" v-model="lastName" placeholder="Last name">
    </div>
</template>

<script lang="ts">
import {
    defineComponent,
    computed
} from 'vue';

import {
    state,
    fullName,
    setFirstName,
    setLastName
} from './stores/user';

export default defineComponent({

    setup() {
        const firstName = computed({
            get: () => state.firstName,
            set: setFirstName
        });
        
        const lastName = computed({
            get: () => state.lastName,
            set: setLastName
        });

        return {
            firstName,
            lastName,
            fullName
        };
    }

});
</script>

Devtools integration

Enabling devtools support is, you guessed it, simple. Just import @harlem/plugin-devtools and register it with your Harlem plugin:

import App from './app.vue';
import Harlem from '@harlem/core';

import createDevtoolsPlugin from '@harlem/plugin-devtools';

import {
    createApp
} from 'vue';

function start() {
    let plugins = [];

    if (process.env.NODE_ENV === 'development') {
        plugins.push(createDevtoolsPlugin({
            label: 'State'
        }));
    }

    return createApp(App)
        .use(Harlem, {
            plugins
        })
        .mount('#app');
}

start();

See the devtools plugin docs for more information on the options available.

Harlem Devtools

At the time of writing this you will need to use the Beta version of the Vue devtools.

Server-Side Rendering

Harlem supports using stores in an SSR application via the SSR plugin (@harlem/plugin-ssr). Refer to the SSR plugin documentation for more information and how to get started. The SSR plugin docs are available here.

Plugins

Harlem is completely extensible through plugins. Feel free to choose from some of the official plugins or write your own. See the plugins documentation from more information on the official set of plugins or how to author your own plugin.

Some of the official plugins include:

  • Devtools (@harlem/plugin-devtools) - The devtools plugin adds Vue devtools integration with your stores to show updates to your state in realtime.
  • Reset (@harlem/plugin-reset) - The reset plugin provides an API to reset stores to their initial state.
  • Snapshot (@harlem/plugin-snapshot) - The snapshot plugin provides an API to snapshot a store's state at a given point and apply it when convenient.
  • SSR (@harlem/plugin-ssr) - The SSR plugin enables support for using Harlem stores in a server-side rendered application.
  • Storage (@harlem/plugin-storage) - The storage plugin provides simple local/session storage synchronisation with your state. This plugin relieves the burden of having to manually save your state to a web storage resource.
  • Transactions (@harlem/plugin-transaction) - The transaction plugin provides an API for defining transactions that run multiple mutations. A transaction can safely rollback mutations in the event of an error.

TypeScript support

Harlem fully supports Typescript - just decorate your mutation with the payload type and Harlem will take care of the rest:

export const setFirstName = mutation<string>('setFirstName', (state, payload) => {
    state.firstName = payload || ''
});

All other types (state, getters etc) are automatically inferred, however should you wish to define your own state type you can do so during store creation:

import {
    createStore
} from '@harlem/core';

interface State {
    firstName?: string;
    lastName?: string;
};

const STATE: State = {
    firstName: 'John',
    lastName: 'Smith'
};

const {
    getter,
    mutation,
    ...store
} = createStore<State>('user', STATE);

In most cases this will be unnecessary but it can be useful for defining nullable fields or fields that don't exist at the time of store creation.

FAQ

What about actions?

Harlem doesn't provide a mechanism for actions - this is by design. Actions are commonly asynchronous methods that contain business logic which group a single mutation or set of mutations together. Harlem leaves your action design up to you. Here is a simple example of an action using Harlem:

import {
    setLoading,
    setUserDetails
} from './mutations';

export async function loadUserDetails(userId) {
    setLoading(true);

    try {
        const response = await fetch(`/api/users/${userId}`);
        const data = await response.json();

        setUserDetails(data);
    } finally {
        setLoading(false);
    }
}

Can I share state between stores?

Certainly - just import the state or getter from one store into the getter you are authoring on another store. For example:

import {
    state as otherState
} from '../other-store;

import {
    getter
} from './store';

export const myNumberGetter = getter('myNumber', state => state.myNumber +  otherState.otherNumber);

This also works for importing getters from other stores. Just remember that to access the value of a getter you will need to use the .value property of the getter. For example, if I had a getter name myGetter and I wanted to use it in another getter I would have to use myGetter.value to access it's raw value.

See the Vue documentation on computeds for more information. Vue Computed.

Does Harlem have a file structure convention for stores?

Short answer, no. Because Harlem attempts to be as unonpinionated as possible that means it's up to you to structure your store how you see fit. That being said here are 2 examples that may give you a headstart:

Single file structure

- stores
    - store1
        state.js
        getters.js
        mutations.js
        actions.js
        store.js
        index.js
    - store2
        state.js
        getters.js
        mutations.js
        actions.js
        store.js
        index.js

Multi-file structure

- stores
    - store1
        - getters
            getter-1.js
            getter-2.js
        - mutations
            mutation-1.js
            mutation-2.js
        - actions
            action-1.js
            action-2.js
        state.js
        store.js
        index.js
    - store2
        - getters
            getter-1.js
            getter-2.js
        - mutations
            mutation-1.js
            mutation-2.js
        - actions
            action-1.js
            action-2.js
        state.js
        store.js
        index.js

In both cases the store.js file and the index.js files would look roughly the same.

// store.js

import STATE from './state';

import {
    createStore
} from '@harlem/core';

export const {
    state,
    getter,
    mutation,
    ...store
} = createStore('store1', STATE);
// index.js - single file structure

export { state } from './store';

export {
    getter1,
    getter2
} from './getters';

export {
    mutation1,
    mutation2
} from './mutations';
// index.js - multi-file structure

export { state } from './store';

export { default as getter1 } from './getters/getter-1';
export { default as getter2 } from './getters/getter-2';

export { default as mutation1 } from './mutations/mutation-1';
export { default as mutation2 } from './mutations/mutation-2';

Credits

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].