Skip to content

Instantly share code, notes, and snippets.

@lxe
Last active September 23, 2018 20:17
Show Gist options
  • Save lxe/27d944bbcf9b09326b3abc765ecfdde1 to your computer and use it in GitHub Desktop.
Save lxe/27d944bbcf9b09326b3abc765ecfdde1 to your computer and use it in GitHub Desktop.
Simple React Flux/Redux Alternative

1. One global mutable (oh no) store:

const Store = {
  name: 'My Name',
  counter: 0
}

window.Store = Store; // Convenient!

2. Top-level root component "subscribes to store updates" by becoming updatable by everything else everywhere for any reason:

class App extends React.Component {
  componentDidMount () {
    this.setState({ updated: 0 }); 
    Store.update = () => {
      // Mutate state somehow to update component tree
      this.setState({ updated: this.state.updated + 1 });
    }
  }

  render() {
    return (
      <React.Fragment>
        <Counter />
        <Greeter />
      </React.Fragment>
    )
  }
}

3. Directly do anything to the store by any and all components or anything else in your app, and then call Store.update()

// No props, no state, no mess
const Counter = () => (
  <React.Fragment>
    <div>Counter is {Store.counter}</div>

    <button
      onClick={() => {
        Store.counter += 1;
        Store.update();
      }}
    >
      Incrememnt
    </button>

    <button
      onClick={() => {
	// Oh no, a mutation!
        Store.counter -= 1; 
        
        // Observable? Nah just a function call.
        Store.update(); 
      }}
    >
      Decrement
    </button>
  </React.Fragment>
);

// No props, no state, no mess
const Greeter = () => (
  <React.Fragment>
    <h1>Hello, {Store.name}</h1>
    <input
      placeholder="Enter Name"
      onChange={e => {
        Store.name = e.target.value;
        Store.update();
      }}
    />
  </React.Fragment>
);

4. Profit:

https://codepen.io/lxe/pen/QVPJVx

5. Don't like to call update()? Use an ES6 Proxy:

let Store;

const storeUpdateHandler = { 
  get (target, key) {
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], storeUpdateHandler);
    }
    return target[key];
  },
  set (target, key, value) {
    Store && Store.update && Store.update();
    target[key] = value;
    return true;
  }
};

Store = new Proxy({
  name: 'TheName',
  counter: 0
}, storeUpdateHandler);

Now you don't have to call Store.update().

FAQs

Q: "What are the limitations of doing things this way?"

A: I don't know.

Q: "Oh no, but how do I debug it? How do I subscribe to store changes?"

A: Use ES6 Proxies:

const debugProxyHandler = { 
  get(target, key) {
    if (typeof target[key] === 'object' && target[key] !== null) {
      return new Proxy(target[key], debugProxyHandler)
    } else {
      return target[key];
    }
  },
  set (target, key, value) {
    debugger;
    return true
  }
};
Store = new Proxy(Store, debugProxyHandler);

Simply paste that ^ into Chrome console. You get just amazing really nice traces where you can find the source of store mutation in a single stack layer.

Q: "Oh no, but you're updating all the things all the time from top down!"

A: That's OK. Haven't seen performance problems yet in my counter/greeter app. Shouldn't react know about these things automagically anyways? I guess I'll need to reinvent redux to solve this at some point.

Q: "This is bad because..."

A: I'm sorry.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment