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

Middleware for debugging? #71

Closed
MattSPalmer opened this issue Jan 4, 2016 · 8 comments
Closed

Middleware for debugging? #71

MattSPalmer opened this issue Jan 4, 2016 · 8 comments

Comments

@MattSPalmer
Copy link

Hi there team. Really enjoy the library.

Was wondering if there's been any consideration thus far for introspection into one's selectors via middleware or some other modification? As apps grow bigger and selectors increase in complexity, I can see a lot of value in having some way to quickly (and visually) trace the origination of an arbitrary selector's value.

All selectors created by createSelector can be described as having X 'ancestors' and Y 'descendants'. Seems to me these relationships lend themselves really well to being represented as a visual hierarchy; a tree, graph, or what have you.

Consider the example in the README:

import { createSelector } from 'reselect'

const shopItemsSelector = state => state.shop.items
const taxPercentSelector = state => state.shop.taxPercent

const subtotalSelector = createSelector(
  shopItemsSelector,
  items => items.reduce((acc, item) => acc + item.value, 0)
)

const taxSelector = createSelector(
  subtotalSelector,
  taxPercentSelector,
  (subtotal, taxPercent) => subtotal * (taxPercent / 100)
)

export const totalSelector = createSelector(
  subtotalSelector,
  taxSelector,
  (subtotal, tax) => ({ total: subtotal + tax })
)

One could conceive of a graph showing the flow of state down through to totalSelector:
image

Nodes on such a graph could contain information such as the number of re-computations on that selector, the last memoized value, when the value last changed, etc.

Curious to know if there's interest in a project that would generate output like the above, or if someone's done similar work already. Either way, I'd love to help out!

@ellbee
Copy link
Collaborator

ellbee commented Jan 5, 2016

This looks neat, thanks for the write-up. I've not seen anything similar. Seems like it might be a neat thing to somehow have in a devtools monitor.

@aarongreenwald
Copy link

I'm also interested in debugging middleware. The fancy graph @MattSPalmer put together would be cool, but I'd start even simpler - logging the memoized values to the console, similar to how redux-logger does on every action. We could do it every action, or only when the memoized value changes.

This would be very useful for me because I use the memoized values in my view layer and I need to be able to check that they are what I think they are when debugging.

I started doing something like this in our application. I'm not sure if it makes sense to include middleware in reselect or package it separately, but I'd be happy to share it if desired.

@MattSPalmer
Copy link
Author

I would be interested to see your approach, @aarongreenwald. I'm also not sure if building middleware capabilities into reselect is the way to go. I do think that without middleware, anything beyond logging memoization behavior will be difficult.

As far as inspecting memoized values, I've had some luck with using a special memoization function that has logging side-effects.

@aarongreenwald
Copy link

Here's what I have for now

const reselectLogger = (selector, name) => ({getState}) => next => action => {
  let oldState = selector(getState());
  let result = next(action);
  let newState = selector(getState());
  if (oldState !== newState) {
    console.log(`Selector recalculated: ${name}`, selector(getState()));
  }
  return result;
};

and then

let middlewares = [
  ...
  reselectLogger(someSelector, 'Selector Name'),
  reselectLogger(anotherSelector, 'Another Selector Name'),
];

const createStoreWithMiddleware = applyMiddleware(...middlewares)(createStore);
const store = createStoreWithMiddleware(rootReducer);

When I get around to it, I'll improve the console.log statement to format the output nicely so that it fits in with the redux-logger output. Also, I'd like to figure out a way to avoid having to include an instance of the middleware for every selector I have. I have some ideas but I think they're overcomplicated.

@kbrownlees
Copy link

You could use a custom memoize function - that would allow you log every selector state change which would be quite cool. https://github.com/rackt/reselect/blob/master/src/index.js#L5

@pke
Copy link

pke commented Mar 5, 2016

Hmmm. I also would like to know when a selector is recalculated. Your middleware @aarongreenwald seems to verbose to setup though. Maybe @MattSPalmer can share some infos about that custom memoization function?

@kbrownlees
Copy link

https://gist.github.com/kbrownlees/93e57729d6ad95f38381/02771956d0ba3f16ca6333b8983af012f55d9695

It works but it hard to read when you have lots of selectors! I did make it so you can do something along the lines of:

createSelector('my awesome selector', selector1, (s1) => s1)

This prepends all the logs with 'my awesome selector', but I am not convinced on this strategy and haven't had much time to spend on it lately (I did have dreams of integrating it with redux devtools so you could see the action -> selectors flow).

It would probably be better if it allowed things to subscribe to changes and provided all the information (old / new inputs and old / new results) rather than just logging the new result.

EDIT: Changed the gist so it now uses a memoize which makes a callback when something changes: https://gist.github.com/kbrownlees/93e57729d6ad95f38381

kbrownlees pushed a commit to kbrownlees/reselect-change-memoize that referenced this issue Apr 10, 2016
Inspired by reduxjs/reselect#71, a memoize
function which will perform a callback everytime the result changes.
@ellbee
Copy link
Collaborator

ellbee commented Apr 16, 2016

@kbrownlees Your module looks interesting. I'm going to close this out and suggest that people check out your library.

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

5 participants