Debugging: Friendly way to view the current state

When I was using React with Redux, there was a browser plugin that would show me the current state (as well as a history of previous states). Is there anything like that for Rescript? I tried printing out my state using Js.log but it’s pretty unintelligible. Is there a way to print out nested variants, maps, and lists, that’s easy to read? Is there a debug print that converts these to something simple like arrays?

I’ve heard there’s some debug mode, or ways to make values easier to view in your console, but I haven’t had much success with getting this stuff to work yet.

1 Like

what’s your setup with debug mode?
do you have a screenshot of “unintelligible output”

If you’re using rescript-react, you can use React DevTools, but you knew that, right?

As for lists, if you mean linked lists, they’re a second-class citizen in ReScript, and in most cases, it’s recommended to use arrays instead. With maps, if you log manually, you can use Belt.Map.toArray.

Variants are trickier since the compiler doesn’t keep the constructor names in the runtime code. But since you’re supposed to pattern match on them exhaustively anyway, you can put log calls in the switch branches. Also, with variants, the compiler should help you a lot during type checking, so if you find yourself debugging runtime behavior for variants often, maybe there’s an opportunity to structure your code differently.

6 Likes

Oh, I actually wasn’t aware that React DevTools worked with this. It doesn’t seem to work for my project, though. When I go to the Components tab in my web inspector and select my app’s root component, the right pane just says “Loading…” forever and logs this to the JavaScript console:

react_devtools_backend.js:12753 Uncaught TypeError: Cannot read property ‘push’ of undefined
at J (react_devtools_backend.js:12753)
at L (react_devtools_backend.js:12787)
at exports.inspectHooksOfFiber (react_devtools_backend.js:12848)
at inspectElementRaw (react_devtools_backend.js:7953)
at Object.inspectElement (react_devtools_backend.js:8223)
at react_devtools_backend.js:10043
at Bridge.emit (react_devtools_backend.js:4465)
at react_devtools_backend.js:10706
at listener (react_devtools_backend.js:11480)

My app’s state is rather big has a lot of nested maps and variants, btw.

I’m still experiencing this error. I’ve narrowed it down and can cause it with a very small snippet of code now. https://github.com/facebook/react/issues/23030

Never mind. I updated rescript-react-update to version 5.0 and now my React DevTools extension works. Thing is, it’s not easy to read Belt.Maps in there. Is there a way to make those more readable inside DevTools?

There’s a Chrome extension for formatting ReasonML values to be more readable. It’ll likely to need updating to work with ReScript.

That chrome extension doesn’t seem to do anything for me. I assume it doesn’t affect React DevTools. It’s supposed to work for Js.log, right? No effect there either. I tried installing it as a dependency, but I don’t understand what I’m supposed to do with this code block:

[@bs.module "reason-console-formatter"]
external install: unit => unit = "default";

install();

As @Hongbo mentioned, can you share what values you are struggling to debug on?

I’d recommend using array instead of list and Js.Dict instead of Belt.Map unless you have a specific reason not to.

Honestly I’d support officially deprecating list and other such non-JS backed values, they trip newcomers up all the time - especially TypeScript developers who I think assume that if something has syntax support it must be ECMAScript and not TS.

Instead of deprecating types (list is already heavily discouraged), I think a better approach would be to add better support to the JS types. Right now, using Js.Dict.t comes with a lot of friction. There’s no way to declare a dict literal (the closest is using Js.Dict.fromArray), and it lacks a lot of common utility functions. There’s no Js.Dict.copy, remove, or forEach, and its map has a weird signature.

Belt.Map will always be necessary because it’s purely functional and backed by an efficient immutable structure, which Js.Dict isn’t. (Not to mention that it can work with any key type.) But even if you don’t need immutability, then it’s still hard to use Js.Dict instead due to the aforementioned API limitations.

4 Likes

IMO the mindset should be like “ReScript is a different language, not just js with types”. we already have flow and ts.
And having a poor standard library is js’s problem. solving that via a rich/reliable std library can be a selling point for rescript not adoption/learning blocker.

6 Likes

Here’s what I’m dealing with:

Belt.Map shows up as k v h l r. I’m using these heavily in my app state. TBH the main reason I’m using ReScript is for its immutability and type system so why would I switch to Js.Dict?

list shows up as hd tl which isn’t terrible. I’m using these whenever I want something to act like a stack, especially in reduce – are you telling me that arrays are still somehow better even for this use case?

My variants are showing up as TAG: 0, _0:. Adding the -bs-g flag makes it add the variant’s name, but only if the variant contains data. scopeType is a variant but all you get is an integer in the React DevTools.

Can this be improved?

Regarding Belt.Map and list, if you’re going to use those then the runtime representation is just a tradeoff you have to make. I remember having the same conversations about Immutable.js back in the day, opaque runtime data is a trade off for guaranteed immutability here.

A middle ground could be immutable wrappers around Js.Dict and array which will be just as safe as the structures you’re using (barring any unsafe casting). See Immutable array implementation? - #2 by spyder

The only thing you lose is pattern matching on lists, but that’s not a massive deal in my experience.

for debugging immutable maps and lists, I’ve found that calling Map.toArray/List.toArray before printing works reasonably well. if you have nested maps/lists you’d need to recursively apply it.

Something you could also try is adding a custom devtoools formatter to chrome.

https://www.mattzeunert.com/2016/02/19/custom-chrome-devtools-object-formatters.html

Old Docs is that still functional?

As Nick mentioned,

@nickretallack, my 2c, this is a long-time problem. The best recommendation we have right now for debugging, is to hand-write functions to convert your custom types into representations which will print to the JS console in a friendly way. E.g.,

module Job = {
  type t =
    | Queued
    | Started(Js.Date.t)
    | Finished(Js.Date.t)

  let debug = job =>
    switch job {
    | Queued => "Job.Queued"
    | Started(time) => `Job.Started(${Js.Date.toISOString(time)})`
    | Finished(time) => `Job.Finished(${Js.Date.toISOString(time)})`
    }
}

module Book = Belt.Map.String
type state = Book.t<Job.t>

let debug = state => state->Book.map(Job.debug)->Book.toArray

This will print to nice-looking strings like:

[
  ["abc", "Job.Queued"],
  ["def", "Job.Started(1973-11-29T21:30:54.321Z)"]
]

Until someone comes up with a better solution, this is the best we can do.

What would a better overall solution look like? I think something much like what genType does, only except instead of generating just TypeScript definitions, it would generate ReScript debug functions for types.