2
\$\begingroup\$

I got frustrated with the jargon and created an alternative state management solution. Its up at https://github.com/smakazmi/react-soliit. It goes something like this

import React from "react";

export default class StateStore<T>  {
  private listeners: Array<Function>;
  constructor(public state: T) {
    this.listeners = new Array<() => void>();
  }

  private fireOnSetState() {
    this.listeners.forEach(l => {
      try {
        l();
      }
      catch { }
    })
  }

  protected setState(newState: Partial<T>) {
    this.state = { ...this.state, ...newState };
    this.fireOnSetState();
  }
  subscribe(callback: () => void) {
    if (this.listeners.indexOf(callback) < 0)
      this.listeners.push(callback);
  }
  unsubscribe(callback: () => void) {
    this.listeners = this.listeners.filter(l => l !== callback);
  }
}

export const withStores = function (WrappedComponent: any, stores: { [index: string]: StateStore<{}> }) {
  return class extends React.Component {
    updateState = () => {
      this.forceUpdate();
    }
    componentDidMount() {
      Object.keys(stores).forEach(k => {
        stores[k].subscribe(this.updateState);
      });
    }
    componentWillUnmount() {
      Object.keys(stores).forEach(k => {
        stores[k].unsubscribe(this.updateState);
      });
    }
    render() {
      return (<WrappedComponent {...this.props} {...stores} />);
    }
  }
}

Can be used as follows

const counterStore = new StateStore({ count: 0 });

const Counter = function({ counterStore }) {
  const increment = () =>
    counterStore.setState({ count: counterStore.state.count + 1 });
  const decrement = () =>
    counterStore.setState({ count: counterStore.state.count - 1 });
  return (
    <div>
      <button onClick={decrement}>-</button>
      <span>{counterStore.state.count}</span>
      <button onClick={increment}>+</button>
    </div>
  );
};
const ConnectedCounter = withStores(Counter, { counterStore });
render(<ConnectedCounter />, document.getElementById("root"));

The sample is available at https://stackblitz.com/edit/soliit-simple-counter

Seems to work fine, at the same time seems too good to be true. So looking for reasons to why to go for Redux, MobX or even Unstated and not something as simple as this.

\$\endgroup\$
  • \$\begingroup\$ When you use well maintained/active packages, you get a lot of extra tools. In Redux, you get the option to have powerful middlewares and good technical support. It's totally fine to make your own state management implementation, but if something goes wrong or you need to extend/add complex functionality, you're on your own. \$\endgroup\$ – Andrew Oct 7 at 6:59
  • \$\begingroup\$ thanks for your comment @andrew. i realize what you're saying though. i was infact looking for technical feedback on the approach that i've used here. for e.g. does it miss any obvious use cases ? any obvious performance issues ? \$\endgroup\$ – Ali Kazmi Oct 7 at 7:08
2
\$\begingroup\$

After looking at the implementation, this is not an implementation of global state management. The entire point of things like redux is so you can directly connect components to a global store. In your case, it looks like every instance of StateStore has its own unique data structure, which seems to imply that you are only going to use StateStore once.

You can only import it at the highest level and keep passing it down in order to persist the data. This has very heavy performance implications. Read up on how React decides on whether it should scan an entire component for any changes (rerenders can severely add up) and how the reconciliation algorithms relates to it.

Your implementation is essentially this.state but also handles functions. Technically the same functionality, but it's not a true global state like how the context api or redux works.

\$\endgroup\$
  • \$\begingroup\$ What if I only ever create one single instance of the store, connect it to the root app component and then in any child component I can interact directly with the store instance and still get updates when the store is updated. I dont necessarily need to connect each child component to the store instance directly or to pass down the store explicitly. \$\endgroup\$ – Ali Kazmi Oct 7 at 17:57
  • \$\begingroup\$ Current set of functions requires you to pass in the entire store, regardless of where you "connect" the component. You should create a scheme that allows you to pick and choose which props you want from your state (aka mapStateToProps), or you're going to have the exact same performance issues, possibly worse. \$\endgroup\$ – Andrew Oct 7 at 18:03
  • \$\begingroup\$ Great advice andew, much appreciated. I've made some changes. I'd appreciate it if you give it a look and let me know what you think. Its still possible to make the entire store(s) accessible to containers but now I use proxies to track which state components depend on. Also, I acknowledge that sometimes its better to expose state selectively, so I added a mechanism for that too. The source is at github.com/smakazmi/react-redeux An example can seen at codesandbox.io/s/github/smakazmi/react-redeux/tree/master/… \$\endgroup\$ – Ali Kazmi Oct 10 at 6:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.