Skip to content
/ hez Public

Flux architecture for React app. Fast and easy to use

Notifications You must be signed in to change notification settings

linq2js/hez

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

40 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Hez

Flux architecture for React app. Fast and easy to use.

Hez requires React hooks, so you must install react 16.7+ and react-dom 16.7+ packages

Features

  1. No reducer
  2. No action creator
  3. Simple and reusable action logic
  4. Easy to write unit-test

Hez in action

  1. Compare to Redux https://codesandbox.io/s/53l6y55kmk

Counter app

import React from "react";
import ReactDOM from "react-dom";
import { createStore, useStore, useActions } from "./hez";

const initialState = {
  count: 0
};

const store = createStore(initialState);

const Up = (state, value) => state.set({ count: state.count + value });
const Down = (state, value) => state.set({ count: state.count - value });
const AddTen = state => Up(state, 10);

const App = () => {
  const count = useStore(store, state => state.count);
  const [up, down, addTen] = useActions(store, Up, Down, AddTen);

  return (
    <div>
      <h1>{count}</h1>
      <button onClick={() => up(1)}>Up</button>
      <button onClick={() => down(1)}>Down</button>
      <button onClick={() => addTen()}>Add 10</button>
    </div>
  );
};

const rootElement = document.getElementById("hez-app");
ReactDOM.render(<App />, rootElement);

Create a store

import { createStore } from "hez";

const store = createStore({
  todos: []
});

Define an action

const AddTodo = (state, text) =>
  state.set({ ...state.get(), todos: state.todos.concat({ text }) });

Dispatch an action

import { useActions } from "hez";

const AddTodoForm = () => {
  const [addTodo] = useActions(store, AddTodo);

  function handleClick() {
    addTodo("new todo");
  }

  return <button onClick={handleClick}>Add Todo</button>;
};

Access state values

import { useStore } from "hez";

const TodoCount = () => {
  const count = useStore(store, state => state.todos.length);

  return <div>{count}</div>;
};

Async actions

const SaveTodos = state => {
  return axios({ url: "save.todo.api", data: state.todos }).then(
    () => alert("succeeded"),
    () => alert("failed")
  );
};

Reuse action logic

const AddOne = state => state.set({ value: state.value + 1 });
const AddTwo = state => {
  AddOne(state);
  AddOne(state);
};

Using state.set

const state = createState({ count: 0 });
state.set({ count: 1 }); // => 1
state.set("count", x => x + 1); // => 2
// state.set(prevState => nextState);

Using state.merge

const store = createStore({
  value1: 1,
  value2: 2
});

const Add1 = state =>
  state.set({
    ...state.get(),
    value1: state.value1 + 1
  });

// using merge will reduce your code
const Add2 = state =>
  state.merge({
    value2: state.value2 + 1
  });

// state.merge(prevState => nextState);

Using immhelper to update state

import update from "immhelper";
import { createState } from "hez";

const state = createState({ value1: 1, value2: 2, todos: [] });
state.set(
  update(state, {
    value1: ["set", 2],
    value2: ["set", 3],
    todos: ["push", "New Todo"]
  })
);

// shorthand
state.set(
  update({
    value1: ["set", 2],
    value2: ["set", 3],
    todos: ["push", "New Todo"]
  })
);

Printing state

import { createState } from "hez";

const state = createState({ count: 1 });
console.log(state); // => [[Proxy]]
console.log(state.get()); // => { count: 1 }

Access state props

import { createState } from "hez";

const state = createState({ count: 1 });
const { count } = state;
const count2 = state.count;
const count3 = state.get("count");
const count4 = state.get(x => x.count);

Dispatching action and handling state change

import { createStore } from "hez";

const store = createStore({ count: 0 });

const Up = (state, value = 1) => state.set("count", x => x + value);

// handle change
store.subscribe((state, e) => {
  console.log(state, e.action);
});

store.dispatch(Up); // => { count: 1 }, Up
store.dispatch(Up, 2); // => { count: 3 }, Up

Using useStore(store, selector, ...cacheKeys)

import React from "react";
import { createStore, useStore, withState } from "hez";

const store = createStore({
  todos: {
    1: "Item 1",
    2: "Item 2"
  }
});
const TodoItem = ({ id }) => {
  // component will re-render once state changed or received new id prop
  const text = useStore(store, state => state.todos[id], id);
  return <div>{text}</div>;
};

const TodoHoc = withState(
  store,
  (state, props) => ({
    text: state.todos[props.id]
  }),
  // cache key factory (optional) should return array
  props => [props.id]
);

const TodoItem = TodoHoc(props => <div>{props.text}</div>);

Using useStoreMemo(store, cacheKeysSelector, stateSelector, ...extraCacheKeys)

import React from "react";
import { createStore } from "hez";

const store = createStore({
  ids: [1, 2],
  todos: {
    1: { text: "Item 1" },
    2: { text: "Item 2" }
  }
});

// selector
const selectIds = state => state.ids;
const selectTodos = state => state.todos;
const selectTodoList = (ids, todos) => ids.map(id => ({ id, ...todos[id] }));

const TodoList = () => {
  const todos = useStoreMemo(store, [selectIds, selectTodos], selectTodoList);

  return <div>{JSON.stringify(todos)}</div>;
};

Using withActions

import React from "react";
import { withActions } from "hez";

const Up = () => {};
const Down = () => {};

const Counter = withActions(store, {
  up: Up,
  down: Down
})(({ up, down }) => (
  <div>
    <button onClick={up}>Up</button>
    <button onClick={down}>Down</button>
  </div>
));

Using Provider to pass down store to component tree

As many frameworks, hez also supports Provider to passing down store

import React from "react";
import { Provider, createStore, useStore } from "hez";

const store = createStore();

const Counter = () => {
  // dont need to call useStore(store, state => state.count);
  const count = useStore(state => state.count);
  return <div>{count}</div>;
};

const App = () => (
  <Provider store={store}>
    <Counter />
  </Provider>
);

State props injection

You can inject state utilize method by using store.inject

Using immhelper for updating state

import update from "immhelper";

const store = createStore({ todos: [] });

store.inject({
  update(state, ...args) {
    const { getState, setState, mergeState } = state;
    setState(update(getState(), ...args));
  }
});

const AddTodo = (state, text) => {
  state.update({
    todos: ["push", text]
  });
};

Using immer for updating state

import produce from "immer";

const store = createStore({ todos: [] });

store.inject({
  update(state, modifier) {
    const { getState, setState, mergeState } = state;
    setState(produce(getState(), modifier));
  }
});

const AddTodo = (state, text) => {
  state.update(draft => {
    draft.todos.push(text);
  });
};

Listen another action dispatching

We can add listener which will be called when specified action dispatched. Use to clean up something

const Login = state => {
  LoadProfile(state);
  LoadSettings(state);
};

const Logout = state => {};

const LoadProfile = state => {
  state.merge({
    profile: {}
  });

  state.on(Logout, CleanProfile);
};

const LoadSettings = state => {
  state.merge({
    settings: {}
  });

  state.on(Logout, CleanSettings);
};

const CleanProfile = state => {
  state.merge({
    profile: undefined
  });
};

const CleanSettings = state => {
  state.merge({
    settings: undefined
  });
};

Using reducer to update state

const state = createState({
  todos: []
});
const AddTodo1 = (state, text) => {
  state.reduce({
    todos: prev => [...prev, text]
  });
};

const AddTodo2 = (state, text) => {
  state.reduce(
    {
      todos: TodoReducers
    },
    {
      type: "add",
      payload: text
    }
  );
};

const TodoReducers = (state, action) => {
  if (action.type === "add") {
    return [...state, action.payload];
  }
  return state;
};

Unit Test

import { createState } from "hez";

const Add = ({ count, set }) => set({ count: count + 1 });

test("should increase count value by 1", () => {
  const state = createState({ count: 0 });
  Add(state);
  expect(state.count).toBe(1);
});

Todo App

About

Flux architecture for React app. Fast and easy to use

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published