Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration with Redux devtools #30

Open
joshburgess opened this issue May 8, 2018 · 8 comments
Open

Integration with Redux devtools #30

joshburgess opened this issue May 8, 2018 · 8 comments

Comments

@joshburgess
Copy link
Contributor

What do you all think of providing some out of box integration with the Redux devtools?

A number of js state management alternatives do this. Libraries like ngrx, react-waterfall, react-automata, an unreleased library I'm currently working on, etc.

I could help work on a PR if people are interested in this.

@quicksnap
Copy link
Collaborator

I would LOVE that, however, we are currently limited by the Bucklescript's internal memory representation of records/variants as arrays; we don't have record keys available for serialization.

Related: rescript-lang/rescript-compiler#2690 and rescript-lang/rescript-compiler#2556

@joshburgess
Copy link
Contributor Author

Interesting. So, I guess this a really practical example of this being a problem, like you were describing in the interview before.

FWIW, PureScript records compile to plain JS objects. I think that's the right way to go, purely for simplicity & interop reasons...

@quicksnap
Copy link
Collaborator

The reasoning behind records as arrays is that Bucklescript is very performance optimized, and working with arrays has a lot of performance benefits.

From what I'm aware, debug output (which should cover this use case) is an upcoming focus of the Reason core team.

I am also not as concerned with lightning-speed performance than I am with great debugging experience, but given that they're working on the debugging story soon, maybe it will be the best of both worlds?

@quicksnap
Copy link
Collaborator

Re: interop, there are mechanisms available for userland Records=>JS.Object: https://bucklescript.github.io/docs/en/generate-converters-accessors.html#convert-between-jst-object-and-record

These require opt-in by the user, however, and aren't good for the general case such as this.

@joshburgess
Copy link
Contributor Author

New BuckleScript features should help with this https://bucklescript.github.io/blog/2018/05/21/release-3-1-0.html

@ambientlight
Copy link
Contributor

ambientlight commented Nov 19, 2018

@joshburgess: It does help. Here is quite an experimental but working example.
Once "bsc-flags": ["-bs-g"] is added to bsconfig.json, Symbol(BsRecord), Symbol(BsVariant) available inside our records/variant objects that we exploit to extract record keys/variant name. Variants need to be parametrized though, plain variants are still compiled to number with -bs-g flag and thus have no meta attached to them.

module ReduxDevTools {
  [@bs.deriving abstract]
  type serializeOptions = {
      symbol: bool,
      replacer: (string, Js.t({.})) => Js.t({.})
  };
  
  [@bs.deriving abstract]
  type composeOptions = {
      name: string,
      serialize: serializeOptions
  };
  
  type extension = Js.t({.});
  type connection = Js.t({.});
  
  [@bs.module "redux-devtools-extension"]
  external _devToolsEnhancer: extension = "devToolsEnhancer";
  let devToolsEnhancer = _devToolsEnhancer;
  
  [@bs.send] external _connect: (extension, composeOptions) => connection = "connect";
  let connect = (instance, options) => _connect(instance, options);
  
  [@bs.send] external _send: (connection, Js.t({.}), Js.t({.})) => unit = "send";
  let send = (instance, action, state) => _send(instance, action, state);
};

type action =
  | Increment
  | Decrement;

let counter = (state, action) =>
  switch action {
  | Increment => state + 1
  | Decrement => state - 1
  };

type appActions = CounterAction(action, Js.Dict.t(string));

type someSubstate = {
  magic: string,
  someMoreMagic: string
}

type appState = {
  counter: int,
  substate: someSubstate
};

let appReducer = (state, action) =>
  switch action {
  | CounterAction(action, _) => {...state, counter: counter(state.counter, action)}
  };

let sarializer: Js.t({.}) => Js.t({.}) = [%raw {|
  function _serialize(obj) {
    const symbols = Object.getOwnPropertySymbols(obj);
    const variantNameSymbolIdx = symbols.findIndex(symbol => String(symbol) == 'Symbol(BsVariant)');
    const recordSymbolIdx = symbols.findIndex(symbol => String(symbol) == 'Symbol(BsRecord)');
    if(variantNameSymbolIdx > -1){
      const variantName = obj[symbols[variantNameSymbolIdx]];
      return {
        ...Object.keys(obj).reduce((target, key) => ({
          ...target,
          ['arg_'+key]: _serialize(obj[key])
        }),{}),
        type: variantName
      }
    } else if(recordSymbolIdx > -1) {
      const keys = obj[symbols[recordSymbolIdx]];
      return keys.reduce((object, key, index) => ({
        ...object,
        [key]: _serialize(obj[index])
      }), {})
    } else {
      return obj
    }
  }
|}];

let serializeWithRecordKeys = (_, value) => sarializer(value)
let options = ReduxDevTools.composeOptions(
  ~name="ReductiveTests",
  ~serialize=ReduxDevTools.serializeOptions(
    ~symbol=true,
    ~replacer=serializeWithRecordKeys
  )
);
let connection = ReduxDevTools.connect(ReduxDevTools.devToolsEnhancer, options);

let devToolsEnhancer = (store, next, action) => {
  let returnValue = next(action);
  ReduxDevTools.send(connection, sarializer(Obj.magic(action)), Obj.magic(Reductive.Store.getState(store)));
  returnValue
};

let store = Reductive.Store.create(
  ~reducer=appReducer,
  ~preloadedState={
    counter: 0, 
    substate: {
      magic: "...",
      someMoreMagic: "well..."
    }},
  ~enhancer=devToolsEnhancer,
  ()
);

ReduxDevTools.send(connection, Obj.magic("INIT"), Obj.magic(Reductive.Store.getState(store)));
let dispatch = Reductive.Store.dispatch(store);
dispatch(CounterAction(Increment, Js.Dict.fromList([("foo", "foo"), ("bar", "someTest")])));

@quicksnap: It would be great to have this kind of devToolsEnhancer available out-of-the-box in reductive. What do you think?

screen shot 2018-11-19 at 2 10 35 pm

screen shot 2018-11-19 at 2 09 53 pm

@ambientlight
Copy link
Contributor

Integration with Redux devtools is now available with ambientlight/reductive-dev-tools

@ambientlight
Copy link
Contributor

ambientlight commented Dec 1, 2019

now with upcoming: What's new in release 7, having records compiled into JS objects may results in cleaner representation in devtools monitor, as currently reductive-dev-tools carries __internal (displayed in the monitor) around to help serialize / deserialize ocaml types into js-objects and back.

I will be updating ambientlight/reductive-dev-tools to accommodate that, and will be very happy if any feedback or suggestions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants