Skip to content

Instantly share code, notes, and snippets.

@drodsou
Last active March 28, 2022 00:31
Show Gist options
  • Save drodsou/dffaaaf25378db7ad1b8e8c789a215fe to your computer and use it in GitHub Desktop.
Save drodsou/dffaaaf25378db7ad1b8e8c789a215fe to your computer and use it in GitHub Desktop.
Synchronous mutable React state (useConstructor)

Synchronous mutable React state

Because asynchronous immutable React state is a tedious overkill for small applications

Class

import React from 'react';

export default class Counter extends React.Component {

  data = {count:1}
  up = ()=>this.forceUpdate();
  
  render() {
    const {data, up} = this;

    return (
      <button onClick={()=>up(data.count++)}>
        {data.count}
      </button>
    )
  }

}

Hook

import React from 'react';

// -- useConstructor helper
// -- contrary to useEffect, this runs BEFORE the first render
function useConstructor(fn) {
  const self = React.useRef({}).current;
  self.forceUpdate = React.useReducer(x=>!x, false)[1];
  if (!self.init) {
    self.init = true; 
    fn(self)
  }
  return self;
}

// -- Sync state 
export default function Counter () {

  const {data, up} = useConstructor((self)=>{
    self.data = {count:1}
    self.up = ()=>self.forceUpdate();
  });
  
  return (
    <button onClick={()=>up(data.count++)}>
      {data.count}
    </button>
  )

}

Shared mutable state

Example implementation for React, for learning purposes, but not recommended as when sharing state you usually want to share actions as well to centralize all types of state changes in one place.

Due to this, better use this instead, that is also compatible with Vanilla or Svelte: https://gist.github.com/drodsou/3856fca1e9da003d7c9c825ee3497be7

import React from 'react';

// -- the store
function createMutableSharedStore(data = {}) {
  const _subs = new Set()

  const store = {
    data,
    up: ()=>{
      for (let sub of _subs) {
        console.log('sub exec'); 
        sub() // forceupdates 
      }
    },
    subscribe : (fn)=>{
      _subs.add(fn); 
      return ()=>_subs.delete(fn) // unsubscribe
    }
  }
  return store;
}

// -- hook helper
function useMutableSharedStore (store) {
  const forceUpdate = React.useReducer(x=>!x, false)[1];
  React.useEffect(()=>{
    return store.subscribe(()=>forceUpdate()) // return unsubscribe
  },[]);
  return store;
}

// - use
const store = createMutableSharedStore({count:2});

// -- example use on hooks
function Counter1 () {
  // -- use store (includes sub/unsub)
  const {data,up} = useMutableSharedStore(store);
  return (
    <button onClick={()=>up(data.count++)}>
      {data.count}
    </button>
  )
}

function Counter2 () {
  // -- use store (includes sub/unsub)
  const {data,up} = useMutableSharedStore(store);
  return (
    <button onClick={()=>up(data.count++)}>
      {data.count}
    </button>
  )

}

// -- example use on class
class Counter3 extends React.Component {
  
  componentDidMount() {
    this.storeUnsubscribe = store.subscribe(()=>this.forceUpdate())
  };
  componentWillUnmount() {
    this.storeUnsubscribe();
  }
  
  render() {
    const {data,up} = store;
    return (
      <button onClick={()=>up(data.count++)}>
        {data.count}
      </button>
    )
  }

}

export default function Page () {
  return <>
    <Counter1/><Counter2/><Counter3/>
  </>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment