All Projects → rich-lab → hodux

rich-lab / hodux

Licence: MIT license
🚀Simple reactive React Hooks state management.

Programming Languages

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

Projects that are alternatives of or similar to hodux

realar
5 kB Advanced state manager for React
Stars: ✭ 41 (+41.38%)
Mutual labels:  reactive-programming, react-hooks
kendo-vue
Issue tracker - Kendo UI for Vue http://www.telerik.com/kendo-vue-ui/
Stars: ✭ 49 (+68.97%)
Mutual labels:  reactive-programming
use-axios-react
React axios hooks for CRUD
Stars: ✭ 31 (+6.9%)
Mutual labels:  react-hooks
fetchye
✨ If you know how to use Fetch, you know how to use Fetchye [fetch-yae]. Simple React Hooks, Centralized Cache, Infinitely Extensible.
Stars: ✭ 36 (+24.14%)
Mutual labels:  react-hooks
use-route-as-state
Use React Router route and query string as component state
Stars: ✭ 37 (+27.59%)
Mutual labels:  react-hooks
react-native-scrollable-animated-header
🤯 React Native Animated Header with ScrollView
Stars: ✭ 112 (+286.21%)
Mutual labels:  react-hooks
react-hooks-lifecycle
⚛️ 🪝 ⏳ React hooks lifecycle diagram: Functional components lifecycle explained
Stars: ✭ 194 (+568.97%)
Mutual labels:  react-hooks
flutter use
Play Flutter Hooks.
Stars: ✭ 150 (+417.24%)
Mutual labels:  react-hooks
typescript-react-boilerplate
👻 Minimal example for React with TypeScript.
Stars: ✭ 17 (-41.38%)
Mutual labels:  react-hooks
use-double-click
React hook for combining double-click function into click event, as well as repeatable double-click
Stars: ✭ 17 (-41.38%)
Mutual labels:  react-hooks
react-facade
Experimental dependency injection for React hooks
Stars: ✭ 95 (+227.59%)
Mutual labels:  react-hooks
react-immer-hooks
Easy immutability in React Hooks with Immer.
Stars: ✭ 45 (+55.17%)
Mutual labels:  react-hooks
react-stripe-script-loader
A React Component that loads Stripe script if necessary and shows React Stripe Elements
Stars: ✭ 22 (-24.14%)
Mutual labels:  react-hooks
use-google-sheets
📝 A React Hook for getting data from Google Sheets API v4
Stars: ✭ 75 (+158.62%)
Mutual labels:  react-hooks
RxjavaSamples
This repo is a container for some samples using RXJAVA2 samples
Stars: ✭ 12 (-58.62%)
Mutual labels:  reactive-programming
react-7h-hooks
(持续增加中)提供一些偏业务的实用 react hooks, 让你每天只工作 7 小时 !
Stars: ✭ 15 (-48.28%)
Mutual labels:  react-hooks
flutter-form-with-validation-BLOC
This form and validation functions are created by using the BLOC pattern with RxDart instead of using StatefulWidget
Stars: ✭ 63 (+117.24%)
Mutual labels:  reactive-programming
eslint-config-ns
ESLint config ready to be used in multiple projects. Based on Airbnb's code style with prettier, jest and react support.
Stars: ✭ 27 (-6.9%)
Mutual labels:  react-hooks
covid-19-stats
Get the latest COVID-19 statistics by country
Stars: ✭ 41 (+41.38%)
Mutual labels:  react-hooks
IntroduceToEclicpseVert.x
This repository contains the code of Vert.x examples contained in my articles published on platforms such as kodcu.com, medium, dzone. How to run each example is described in its readme file.
Stars: ✭ 27 (-6.9%)
Mutual labels:  reactive-programming

English | 简体中文

hodux

🚀Simple reactive React Hooks state management. Made with ES6 Proxies.

Build Status Coverage Status NPM version size React

Table of Contents

Introduction

  • Reactive data flow.
  • Selector hook allows extracting state from store, and high-performance optimized.
  • Perfectly TypeScript support.

hodux

import { store, useSelector } from 'hodux';

// create an observable object
const counter = store({
  num: 0,
  other: '',
  inc() { counter.num += 1; }
});

// select state from store
export default function Counter(props) {
  const num = useSelector(() => counter.num);
  // or you can do some compute in component
  // const total = useSelector(() => counter.num + props.step);

  return <div onClick={counter.inc}>The num:{num}</div>;
}

🔗 Try it online

Edit

🔨 Installation

npm install --save hodux
# or
$ yarn add hodux

📖 API

store(model)

  • Signature: function store<M extends object>(model: M): M
  • Description: pass in a pureModel or viewModel and returns a proxied-based observable object, and the observable object behave like origin object(just normal js object), it's just a wrapper of ES6 Proxy binding.
Create store with viewModel
// stores/counter.js
const counter = store({
  count: 0,
  inc() {
    counter.count++;
  },
  // Async operations can be expressed with the standard async/await syntax
  async incx() {
    await wait(1000);
    counter.count += 1;
  }
});

export default counter;
Create store with pureModel
// stores/counter.js
export default store({ count: 0 });

// src/Counter.js
import counter from './stores/counter';
// changing data anywhere outside the store, and the components can perceive this changes
const incx = async () => {
  await wait(1000);
  counter.count += 1;
};

export function Counter() {
  const count = useSelector(() => counter.count);
  return <div onClick={incx}>{count}</div>;
}
Lazy creates
// stores/counter.js
export default (initalCount = 0) => {
  const state = store({ count: initalCount });

  function inc() {
    state += n;
  }

  async function incx() {
    await wait(1000);
    state.count += 1;
  }

  return { state, inc, incx }
}
Create complex or large store(any valid JS structure)
// stores can include nested data, arrays, Maps, Sets, getters, setters, inheritance, ...
const person = store({
  // nested object
  profile: {
    firstName: 'Bob',
    lastName: 'Smith',
    // getters
    get name() {
      return `${person.firstName} ${person.lastName}`
    },
    age: 25
  },
  // array
  hobbies: [ 'programming', 'sports' ],
  // collections
  familyMembers: new Map(),
});

// changing stores as normal js objects
person.profile.firstName = 'Daid';
delete person.profile.lastName;
person.hobbies.push('reading');
person.familyMembers.set('father', father);
person.familyMembers.set('mother', mother);

useSelector(selector, config?)

  • Signature: function useSelector<V>(selector: Selector<V>, config?: Config<V>): V
  • Description: extracts state from store as needed, the components will not re-render unless any selected state changes.

Maybe it's the main difference with react-redux's useSelector(), because react-redux call selector whenever store state changes even not selected at all(react-redux internal decides if makes re-render), so you do't need to use any cache selector library(such as reselect) with useSelector.

useSelector accepts two parameters:

  • the first parameter is a selector function which works as observer API in reactivity system. It subscribes the selected state and diff the previous returned value with the next one to decide if or not re-render. Maybe you can do some compute with state in useSelector and takes result as the return value.

  • the second is an optional config object

    • equals: the compare function between previous return value and the next return value, the defalut is equality

    • debugger: the debugger function passed to @nx-js/observer-util

Returns basic type(is recommended)
function Counter() {
  const num = useSelector(() => counter.num);
  
  return <div>{num}</div>;
}
Computed(calculation cache)
function App() {
  const computed = useSelector(() => {
    const items = store.items; // select items from store

    return items.reduce((acc, item) => acc + item.value, 0);
  });
  
  return <div>{computed}</div>;
}
Select state from multiple stores
function CompWithMutlStore() {
  // whenever the `count` from store1 or the `step` from store1 changes the compoent will re-render, 
  // so the `result` is always be the newest value
  const result = useSelector(() => store1.count + store2.step);
}
You should pass in equals function when returns complex types
function TodoView() {
  const [isEmpty, hasCompleted, allCompleted, active, filter] = useSelector(
    () => [
      todoStore.isEmpty,
      todoStore.hasCompleted,
      todoStore.allCompleted,
      todoStore.activeType,
      todoStore.filterType
    ],
    { equals: _.equals } // use lodash/isEqual
  );
  ...
}

🚨Attention please, selector should not returns non-serializable value such as function, Symbol or ES6 collection, because they are incomparable, you should select out plain serializable objects, arrays, and primitives. This issues is similar to react-redux hooks, check the document or this issue, but the target model pass to store() has no this limitations, you should convert non-serializable to serializable before returning.

🚨You should returns serializable value
function Component() {
  // DON'T DO THIS
  const familyMemebers = useSelector(() => person.familyMemebers);
  // DO THIS
  const [father, mother] = useSelector(() => [
    person.familyMemebers.get('father'),
    person.familyMemebers.get('mother')
  ]);
  ...
}

connect(selector, ownProps?)

function connect<V extends {}, OwnProps = {}>(
  selector: Selector<V, OwnProps>,
  config?: Config<V>
): (classComponent: C) => ConnectedComponent<V, OwnProps>

An HOC wrapper of useSelector to connect store state to the class components, and is only for class components.

connect accepts two parameters:

  • selectorWithProps(ownProps?): familiar to selector, but the difference is selectorWithProps must return object type(such as mapStateToProps in react-redux), selectorWithProps accepts the connected component's props as parameter.

  • config: same as useSelector's config parameter

Class components
const counter = store({
  n: 0,
  inc() { counter.n += 1; }
});

const selectToProps = () => ({ n: counter.n });

class Counter extends Component {
 render() {
   return <div onClick={counter.inc}>{n}</div>;
 }
}

export default const ReactivedCounter = connect(selectToProps)(Counter);
ownProps
const selectToProps = (props) => ({
  step: props.step,
  n: testStore.n
});

class Counter extends React.Component {
  state = { n: this.props.n }
  inc() {
    const n = this.state.n + this.props.step;
    this.setState({ n });
  }
  render() {
    return <div onClick={() => this.inc()}>{this.state.n}</div>;
  }
}

const Connected = connect(selectToProps)(Counter);

render(<Connected step={10} />);

<HoduxConfig equals={fn} debugger={fn} />

  • Type: React.FunctionComponent<React.PropsWithChildren<Config<any>>>
  • Description: The global config Provider.
function consoleLogger(e) {
  if (e.type !== 'get') {
    console.log(`[${e.type}]`, e.key, e.value);
  }
}

ReactDOM.render(
  <HoduxConfig debugger={consoleLogger}>
    <App />
  </HoduxConfig>,
  document.getElementById('root')
);

batch(fn)

  • Signature: function batch(fn: Function) => void
  • Description: a wrapper of unstable_batchedUpdates, to prevent multiples render caused by multiple store mutations in asynchronous handler such as setTimeout and Promise, etc. If you experience performance issues you can batch changes manually with batch.

The React team plans to improve render batching in the future. The batch API may be removed in the future in favor of React's own batching.

const listStore = store({
  loading: false,
  list: []
});
listStore.load = async () => {
  testStore.loading = true;

  const list  = await fetchData();

  batch(() => {
    testStore.loading = false;
    testStore.list = list;
  });
}

💿 Run examples locally

The examples folder contains working examples. You can run one of them with

$ cd examples/[folder] && npm start

then open http://localhost:3000 in your web browser.

🎁 Acknowledgements

Hodux is Inspired by react-easy-state but considered for React Hooks.

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].