Giter Site home page Giter Site logo

rxsv's Introduction

rxsv

Actions Status lerna

Framework agnostic minimal state management library based on RxJS, heavily inspired by Redux and Redux-Observable with limited boilerplate and TypeScript first approach.

Although the library is framework agnostic and it can be used with any framework, it provides especially smooth integration with Vue.js using vue-rx. It has been battle tested in few small to medium projects currently running on production.

Installation:

npm install rxjs @rxsv/core
npm install vue-rx # only if you are using vue

Vue Example:

///////////////// todo module

// todoState.ts

import {
    U,
    ActionsUnion,
    createReducer,
    Effect,
    fromActions,
    select,
    createStore,
} from '@rxsv/core';

type Todo = { id: string; text: string; isDone: boolean };

// create type-safe actions and action creators in one go using `U`
export const Actions = U.createUnion(
    U.caseOf('ADD_TODO')<Todo>(),
    U.caseOf('REMOVE_TODO')<Todo['id']>(),
    U.caseOf('UPDATE_TODO')<Todo>(),
);

// infer union type of all actions from `U`
type Actions = ActionsUnion<typeof Actions>;

// create reducer function using `createReducer` and infered action union type
// `createReducer` will force you to cover all cases, if you don't want this behaviour consider using simple switch
export const todosReducer = createReducer([] as Todo[])<Actions>({
    ADD_TODO: (state, { payload }) => [...state, payload],
    UPDATE_TODO: (state, { payload }) =>
        state.map(todo => (todo.id === payload.id ? payload : todo)),
    REMOVE_TODO: (state, { payload }) => state.filter(({ id }) => id !== payload),
});

// infer state type from the reducer
type State = ReturnType<typeof reducer>;

// handle side effects using rxjs based sagas.
// The same concept and almost the same (there is no support for DI in rxsv's effects) API as in `https://redux-observable.js.org/`
export const todosEffect: Effect<Actions, State> = (action$, state$) =>
    action$.pipe(
        fromActions(Actions.ADD_TODO),  // fromActions works only with `U`. It will infer the action type from `U` of arbitrary length
        withLatestFrom(state$), // you could access state in a effects like this
        tap(([action, state]) => console.log('todo added'))),
    );

// You can use `reselect` but in most cases you won't need selector library, rxjs is more than enough
// use `select` which has built in memoization, or use `distinctUntilChanged` operator directly
export const todosSelector = (state$: Observable<State>) =>
  combineLatest(
    state$.pipe(select(a => a)), // take part(s) of the state
    state$.pipe(select(a => a.length)),
    (todos, length) => todos.map(todo => ({ todo, length })) // do your projection
  );


///////////////// users module

// usersState.ts

// In most cases you'd want to use `U` and `createReducer`.
// But there are situations when having simple record with `createAction` and switch based reducer is helpful

import { createAction, ActionsUnion } from '@rxsv/core';

const USER_CHANGED = 'USER_CHANGED';

const UserActions = {
    userChanged: () => createAction(USER_CHANGED),
};

export type UserActions = ActionsUnion<typeof UserActions>;

import { Reducer } from '@rxsv/core';
import { AppAction } from '@rootStore';

const initialUserState = {
    sth: '',
};

type UserState = typeof initialUserState;

export const userReducer: Reducer<AppAction, UserState> = (state = initialUserState, action) => {
    switch (action.type) {
        case USER_CHANGED:
            return {
                ...state,
                sth: 'hardcoded',
            };
        default:
            return state;
    }
};

import { ofType } from '@rxsv/core';
import { AppEffect } from '@/rootStore';

const userChangedEffectEffect: AppEffect = action$ =>
    action$.pipe(
        ofType(USER_CHANGED), // `ofType` will work only with simple actions created by `createAction`. It loses type safety for 4 or more elements
        debounceTime(1000),
        mapTo(TodoActions.REMOVE_TODO('1'))
    );

///////////////// rootState.ts

// this is supposed to be root of your applications
// here you can combine application modules, inject dependencies to your effects .etc

import { Store, Effect, combineReducers, combineEffects } from '@rxsv/core';
import { TodosActions, todosReducer, todosEffect } from '@/modules/todos/store';
import { UserActions, usersReducer, usersEffect } from '@/modules/users/store';

// combineReducers works the same way as in `Redux`
// it relies on the `===` comparisment so never mutate your state data!
const rootReducer = combineReducers({ users: usersReducer, todos: usersReducer });

// combineEffects will merge all of your effects into one super-effect ๐Ÿ’ฅ Just as in `Redux-Observable`
const rootEffect = combineEffects(usersEffect, todosEffect);

export const rxStore = createStore(rootReducer, rootEffectFactory);

export type AppAction = VisActions | UserActions;
export type AppState = ReturnType<typeof rootReducer>;
export type AppEffect = Effect<AppAction, AppState>;
export type AppStore = Store<AppAction, AppState>;

///////////////// main.ts
import VueRx from 'vue-rx';
import { rxStore } from '@/rootStore';

Vue.use(VueRx);

// you can set store as a global property for less boilerplate,
// (remember about adding appropriate typings that are extending Vue namespace)
// However such setup doesn't work in the embeddable applications
// and might not be that clear
Vue.prototype.$rxStore = rxStore;

Connecting to the Vue App:

If you don't want global property, the store could be initialized in vue's Provide and injected to the components through Inject

<template>
    <div id="App">
        App
    </div>
</template>

<script lang="ts">
import { Component, Vue, Prop, Provide } from 'vue-property-decorator';
import { Observables } from 'vue-rx';
import * as RxSV from 'rxsv';

import { rootReducer, rootEffect, AppStore } from '@/rootStore';

@Component
export default class App extends Vue {
    @Provide('rxstore')
    private get rxStore(): AppStore {
        const store = RxSV.createStore(rootReducer, rootEffect);

        this.$subscribeTo(store.action$, logAction);

        return store;
    }
}
</script>

Usage in Vue components:

Thanks to the vue-rx observables will be unpacked so their values could used without any headache and be passed to the template to render or used as a prop to other components

<template>
  <p>
    <button @click="onClick">
      dispatch
    </button>
    <SomeOtherComponent :todos="todos$" />
    <p> {{ isInital$ }} </p>
  </p>
</template>

<script lang="ts">
import { Component, Vue,  Inject } from 'vue-property-decorator';
import { select, Store } from 'rxsv';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { Observables } from 'vue-rx';

import SomeOtherComponent from './SomeOtherComponent.vue';
import { TodosActions, todosSelector } from '@/modules/visualization/store';
import { AppStore } from '@/rootStore'

@Component<Home>({
    components: {
        SomeOtherComponent,
    },
    subscriptions(): Observables {
        const { state$ } = this.rxStore;

        return {
            todos$: todosSelector(state$),
            isInital$: state$.pipe(
                select(usersSelector),
                map(el => el.length > 0)
            ),
        };
    },
})
export default class Home extends Vue {
    @Inject('store') public readonly rxStore!: AppStore

    private onClick(): void {
        this.rxStore.action$.next(TodosActions.ADD_TODO({ id: 1, text: "kek", isDone: false }));
    }
}
</script>

rxsv's People

Contributors

grzegorz-bielski avatar greenkeeper[bot] avatar dependabot[bot] avatar grzegorzkazana avatar snyk-bot avatar

Watchers

James Cloos avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.