Coder Social home page Coder Social logo

reselect-tools's Introduction

Reselect Tools

Travis npm package Coveralls

Tools for working with the reselect library: Check selector dependencies, inputs, outputs, and recomputations at any time without refreshing!

Graph

export const data$ = (state) => state.data;
export const ui$ = (state) => state.ui;
export const users$ = createSelector(data$, (data) => data.users);
export const currentUser$ = createSelector(ui$, users$, (ui, users) => users[ui.currentUser]);
...

// in configureStore.js
import * as selectors from './selectors.js'
import * as ReselectTools from 'reselect-tools'

ReselectTools.getStateWith(() => store.getState())  // allows you to get selector inputs and outputs
ReselectTools.registerSelectors(selectors) // register string names for selectors
...
ReselectTools.checkSelector('currentUser$')
=> {
	inputs: [{currentUser: 1}, users: {1: {name: 'sam'}}]
	outputs: {name: 'sam'},
	dependencies: [ui$, users$],
	recomputations: 1,
    isNamed: false,
    selectorName: 'currentUser$'
}
selectorGraph()
=> {
	nodes: {
		"data$": {
			name: "data$",
			recomputations: "N/A"
		},
		"ui$": {
			name: "ui$",
			recomputations: "N/A"
		},
		"users$": {
			name: "user$",
			recomputations: 1
		},
		"currentUser$": {
			name: "currentUser$",
			recomputations: 1
		},
	},
	edges: [
		{ from: users$, to: data$ },
		{ from: users$, to: data$ },
		{ from: currentUser$, to: users$ },
		{ from: currentUser$, to: ui$ },
	]
}

Table of Contents

Motivation

It's handy to visualize the application state tree with the Redux Devtools. But I was using selectors a lot, and there was no easy way to visualize the computed state tree. So, I created this library to output graphs like this one:

Graph

This library was intended to be used with the chrome extension. However, it can be still be useful without the chrome extension installed. The chrome extension will be useless without this library.

See the original reselect issue here.

Getting Started

Firstly, I apologize in advance that this section is required. It would be great to match the experience of installing redux devtools or react's. Hopefully the tools will be more tightly integrated with reselect at some point and these steps won't be necessary.

  1. Install the Package

     npm install -s reselect-tools
    
  2. Grab the Chrome Extension

  3. Building the Graph:

    In order to start building out the selector graph, we need to tell the devtools about the selectors.

    import { registerSelectors } from 'reselect-tools'
    registerSelectors({ mySelector$ })
    

    If you're keeping all your selectors in the same place, this is dead simple:

    import * as selectors from './selectors.js'
    registerSelectors(selectors)
    

    That's it! At this point you should be able to open the devtools and view the selector graph.

    The tools will automatically discover and name dependencies of the selectors. If you want to override the name of a selector, you can do so:

    const foo$ = createSelector(bar$, (foo) => foo + 1);
    foo$.selectorName = 'bar$' // selector will show up as 'bar'
    
  4. Checking Selector Inputs and Outputs

    Imagine that your tests are passing, but you think some selector in your app might be receiving bad input from a depended-upon selector. Where in the chain is the problem? In order to allow checkSelector and by extension, the extension, to get this information, we need to give Reselect Tools some way of feeding state to a selector.

    import store from './configureStore'
    ReselectTools.getStateWith(() => store.getState())
    

Example

The example is running here. Grab the extension and take a look!

npm run example

API

getStateWith(func)

getStateWith accepts a function which returns the current state. This state is then passed into checkSelector. In most cases, this will be store.getState()

checkSelector(selector)

Outputs information about the selector at the given time.

By default, outputs only the recomputations of the selector. If you use getStateWith, it will output the selector's input and output values. If you use registerSelectors, you can pass it the string name of a selector.

const two$ = () => 2;
const four$ = () => 4
const mySelector$ = createSelector(two$, four$, (two, four) => two + four)
registerSelectors({ mySelector$ })
getStateWith(() => null)

checkSelector('mySelector$')  // {
									 inputs: [2, 4],
									 output: 6,
									 dependencies: [two$, four$],
									 recomputations: 1,
								 }

selectorGraph(selectorKey = defaultSelectorKey)

selectorGraph outputs a POJO with nodes and edges. A node is a selector in the tree, and an edge goes from a selector to the selectors it depends on.

selectorGraph()
//  {
//  	nodes: {
//  		"data$": {
//  			name: "data$",
//  			recomputations: "N/A"
//  		},
//  		"ui$": {
//  			name: "ui$",
//  			recomputations: "N/A"
//  		},
//  		"users$": {
//  			name: "user$",
//  			recomputations: 1
//  		},
//  		"currentUser$": {
//  			name: "currentUser$",
//  			recomputations: 1
//  		},
//  	},
//  	edges: [
//  		{ from: users$, to: data$ },
//  		{ from: users$, to: data$ },
//  		{ from: currentUser$, to: users$ },
//  		{ from: currentUser$, to: ui$ },
//  	]
//  }

Using custom selectorKeys

Nodes in the graph are keyed by string names. The name is determined by the selectorKey function. This function takes a selector and outputs a string which must be unique and consistent for a given selector. The defaultSelectorKey looks for a function name, then a match in the registry, and finally resorts to calling toString on the selector's resultFunc.

See the tests for an alternate selectorKey.

registerSelectors(keySelectorObj)

Add a named selector to the graph. Set selector names as keys and selectors as values.

Without The Extension

If you're using an unsupported browser, or aren't happy with the extension, you can still get at the data.

The dev tools bind to your app via this global:

  window.__RESELECT_TOOLS__ = {
    selectorGraph,
    checkSelector
  }

Even without the devtools, you can call __RESELECT_TOOLS__.checkSelector('mySelector$') from the developer console or __RESLECT_TOOLS__.selectorGraph() to see what's going on. If the JSON output of the graph is hard to parse, there's an example of how to create a visual selector graph here.

License

MIT

reselect-tools's People

Contributors

knpwrs avatar skortchmark9 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

reselect-tools's Issues

Question: running in development versus production

Good morning. First and foremost, thank you for the great work on this tool! I have only started using it, but already it has been a great help in getting our selector optimizations underway.

From the examples provided, it seems the reselect-tools would be run in both development and production. Have you noticed a perceivable performance impact of running this in production? Or are the examples more for simplicity and we should configure our app in some way to only run this tool in development? If we should only run this in development, I am happy to submit a PR to the docs with some ways to accomplish this or if you have thoughts on how it would best be handled.

Again, thanks for all the hard work!

Instrument on Demand

I was hoping to avoid a way to depend on the library for my own application bundle - this seems like it gets us most of the way there. Of course, it requires that you expose your selectors and state on the global object.

(function() {
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = "https://unpkg.com/reselect-tools/dist/reselect-tools.js";
    document.head.appendChild(script);

    script.onload = () => {
        ReselectTools.registerSelectors(window.Selectors);
        ReselectTools.getStateWith(() => window.store.getState());
    };
})();

Graph is missing. No edges

I am using reselect@4 but there are still not arrows/edges in the graph.
Screenshot 2019-12-09 at 14 07 05

I manually checked the lib and works fine, I can output the:

[ { from: 'getEnv', to: 'getApiUrl' }, ... ]

but the window.__RESELECT_TOOLS__.selectorGraph() gives back edges: []

FYI, output and input in devtool are undefined.

My app works fine. I am doing this

    R.registerSelectors(selectors);
    R.selectorGraph();

Please let me know if I'm missing something

Exception handling in selectorGraph()

Originally suggested by @trungdq88 on #5

Exception handling: Sometimes it shows "Could not load selector graph", I figure out it is because RESELECT_TOOLS.selectorGraph() was throwing exceptions (null handling issue in selector logic), after fixing it all, I could get the graph rendered nicely. To improve this, I we should have a try/catch in this line https://github.com/skortchmark9/reselect-tools/blob/master/src/index.js#L59 and then in the output panel, we can show the exception detail.

This will make the tool more robust and useful.

How to use createSelectorCreator with reselect-tools

I'm creating a custom selector with my own deep equality check like:

const createDeepEqualSelector = createSelectorCreator(defaultMemoize, equals);

However, it's not clear how to use with with reselect-tools.

Thank you

...for creating this awesome project. I would buy you a beer ๐Ÿ‘

Extension marked as malware by Google

Hey,

I just got a notification in my browser that this extension is malware and has been automatically disabled. Any idea what's happened?

Was the listing compromised and if so do we know when? It would be nice to have a timeline so I can assess the impact. The page for the extension on the store is just a 404 now.

How to graph selector factories?

Selector factories are common practice in Reselect. How can we register these? Typically we have multiple instances of the selector for different components, but they all have the same name.

https://github.com/reduxjs/reselect/tree/0c2573987627d620ac5c6d1469eb222b26a867e7#q-how-do-i-create-a-selector-that-takes-an-argument

https://github.com/reduxjs/reselect/tree/0c2573987627d620ac5c6d1469eb222b26a867e7#q-can-i-share-a-selector-across-multiple-component-instances

NPM version out of date

Thanks a ton for creating this! Would you mind updating the module to a new version on npmjs? The only version there is quite out of date and does not work with the instructions provided by the github README.

Support Immutable values

Currently, if a selector outputs Immutable values these are shown in the state tree in their rawest object form e.g. Immutable.Map({a:1}) becomes

{
  size : 1
 __altered : false
 _root:
  entries : [
   "a"
  ]
  ownerID: {}
}

rather than

{
  a : 1
}

Immutable.js rendering.

Hey,
The redux-tools natively support rendering Immutable.js data structures,
it would be really nice if reselect-tools could do the same.
I could work it out and create a pull request, if you're interested?

Cheers,
jp

reselect-tools not receiving dependencies

Hi, thanks for making this! I have implemented reselect-tools in my app, and I have the Chrome extension. The nodes are showing up, but the dependency graph is not being drawn.

I pulled up the demo app you have on the repo, and it seems the same thing is happening. It seems that reselect-tools is looking for the selector dependencies to be returned from reselect but selector.dependencies always returns undefined.

Support groups of selectors

There are 27 files in my selectors folder and they share selector names like id so it is always useless to see a flat structure. Here is the picture after registering only 4 of 27 files:

image

Here is a typical structure of my files containing selectors and how I register them. Maybe I am doing something wrong.

selectors/accessTokens.js

import { createSelector } from 'reselect'

import * as entities from './entities'

const ids = state => {
  return state.accessTokensList.get('accessTokenIds')
}

export const fetching = (state) => {
  return state.accessTokensList.get('fetching')
}

export const listEntities = createSelector(
  ids, entities.accessTokens,
  (ids, entities) => ids.map(id => entities.get(`${id}`))
)
...

selectors/entities.js

import { Map } from 'immutable'

export const ticketingSystems = state => state.entities.get('ticketingSystems') || Map()
export const tickets = state => state.entities.get('tickets') || Map()
export const ticketFieldValues = state => state.entities.get('ticketFieldValues') || Map()
...

selectors/audits.js

import { createSelector } from 'reselect'

import * as entities from './entities'

const ids = state => {
  return state.auditsList.get('auditIds')
}

export const fetching = (state) => {
  return state.auditsList.get('fetching')
}

export const listEntities = createSelector(
  ids, entities.audits,
  (ids, entities) => ids.map(id => entities.get(`${id}`))
)

export const selectedAuditId = (state) => {
  return state.auditsList.get('selectedAuditId') || null
}

export const selectedAudit = createSelector(
  selectedAuditId, entities.audits,
  (id, entities) => entities.get(`${id}`)
)
...

...

selectors/index.js

import * as accessTokens from './accessTokens'
import * as audits from './audits'
import * as ticketStatuses from './ticketStatuses'
import * as entities from './entities'

export {
  accessTokens,
  audits,
  ticketStatuses,
  entities
}

store.js

import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createSagaMiddleware from 'redux-saga'
import createRootReducer from 'reducers/rootReducer'
import { appHistory } from 'appHistory'
import root from 'sagas/root'

import * as selectors from 'selectors'
import * as ReselectTools from 'reselect-tools'

const connectedRootReducer = createRootReducer(appHistory)
const initialState = {}
const sagaMiddleware = createSagaMiddleware()

const middleware = [
  routerMiddleware(appHistory),
  sagaMiddleware
]

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const enhancer = composeEnhancers(
  applyMiddleware(...middleware)
)

const store = createStore(connectedRootReducer, initialState, enhancer)
sagaMiddleware.run(root)

ReselectTools.getStateWith(() => store.getState())
ReselectTools.registerSelectors(selectors.accessTokens)
ReselectTools.registerSelectors(selectors.audits)
ReselectTools.registerSelectors(selectors.ticketStatuses)
ReselectTools.registerSelectors(selectors.entities)

export { store }

I think there should be a way to add selectors specifying the name it their groups like 'audits', 'entities' etc. In the code they are imported as

import * as auditsSelector from 'selectors/audits'

so it will be clear for developers.

works with react-native?

any idea how to get this to work (if possible) with react-native development? I tried it out of the box to no avail.

Feature Request: Reset "Recomputations" Count

As a user, I want to be able to reset the Recomputations count so that I can see how many recomputations occurred for a particular action within my application.

Current behavior: the Recomputations field continues to grow in size as more and more interactions are triggered.


Note: I do not know if this will require a change to the Reselect Devtools extension or if this is an internal mechanic that is managed by reselect-tools itself (it may be both). If the case is the latter, this issue can be moved to the reselect-tools repo instead.

Absent Reselect panel in Chrome

Hi.

I installed extension for chrome.
I have icon in the top near the address bar,
image
but I haven`t any reselect panel in dev-tools among other tools.
image
an not here, in menu "more tools"
image

Add selector computation timings to JSON output

Awesome library!
It would be really great if there was a way to time all registered selectors. We also use selectors a lot, and, although the graph from reselect-tools is really helpful, it would be great to be able to track time spent computing each selectors output. Is there any easy way to achieve this? I'd be happy to try make a PR if you point me in the right direction. Unless there is already something out there.
Thanks!

Request to update repository with version 0.0.2 from Chrome App Store

Hi @skortchmark9 ,

Thanks for the great work on this extension! I was wondering if you might be able to upload the source of the v0.0.2 version of this extension to Github, which has some functionality that isn't in v0.0.1, like enabling highlighting of the most recomputed nodes, or counting the number of recomputations. (very useful features, by the way!)

I'm experimenting with some different graph layouts, and was hoping to work with the same version of the source code that's in the chrome extension store.

Thanks for your consideration!

`ReselectTools.registerSelectors()` fails if the given object has a null value property

Hi !

I was starting to use the tool, and followed the setup code like this

in my storeCreator.js (setups the store)

import * as selectors from 'selectors/selectors'

ReselectTools.getStateWith(() => store.getState())
ReselectTools.registerSelectors(selectors)

And I was getting this exception

image

My selectors file seems a regular file

export { selectedObject, selectedObjects } from './objects'
export { vmLoading, vmLoaded, tasksComply } from './project'

I started to inspect the "selectors" object (from import * as selectors) and noticed that it has a couple of extra fields set by babel or whatever the underlying impl of the es modules creates.
And seems like one of those fields was null and made the library break.

I solved it by filtering some properties like this

    const distilledSelectors = Object.keys(selectors).reduce((acc, k) => {
      const v = selectors[k]
      if (v) acc[k] = v
      return acc
    }, {})
    ReselectTools.registerSelectors(distilledSelectors)

But maybe it would be great if the library already fixes this by checking for "selector" to be truthy.

Thanks !

Can't open dev tools window

Whenever I click on the extension it either takes me to your github page or the page for the extension. I looked for another panel in the devtool window like in your screen shot but didn't see any.

Support for re-reselect

I am a bit suprised to not find anything on google or the issue log here, so maybe I am just doing it wrong :)

We have a lot of re-reselect selectors, but as soon as I add them to registerSelectors, I get log-spam from re-reselect, because the re-selector's keySelector is called without the mandatory cache key parameter.

index.js:334 [re-reselect] Invalid cache key "undefined" has been returned by keySelector function.

I guess the tools would need some way to give the keySelector the correct parameters, eg a helper that you could provide or by inspecting the cached selectors. I managed to get a partial view on the selector by fetching one of the cached selectors directly:

const cachedSelector = selector.getMatchingSelector(null /* this normally the state */, 'default' /* cache key param */);
reselectTools.registerSelectors({ cachedSelector });

immediate selectorGraph() change logging

Currently, changes in state will not immediately be reflected in reselect-tools unless selectorGraph() is fired (i.e. on clicking "refresh" in the extension). I've overcome this by adding some redux middleware:

const reselectTools = store => next => action =>{

  const nodes = selectorGraph().nodes

  let result = next(action)
  
  if (typeof action === "function") return result

  const nextNodes = selectorGraph().nodes

  const nnKeys = Object.keys(nextNodes) // ["data$", "ui$"]

  // log if action changed any selectors
  nnKeys.forEach(k=>{
    if (nextNodes[k].recomputations!= nodes[k].recomputations) console.log({
      ...checkSelector(k), action
    })

  })

  return result

}

image

Now I can at least search in the console for a selector and see the action that triggered it whenever it's recomputed. It'd be great to combine this with the extension search filter.
It seems like this should be the default, or maybe there is a better way to do this? Also, any way to remotely update the extension?

Support groups of selectors

I am creating a copy here as it needs to be fixed in both lib and extension.

There are 27 files in my selectors folder and they share selector names like id so it is always useless to see a flat structure. Here is the picture after registering only 4 of 27 files:

image

Here is a typical structure of my files containing selectors and how I register them. Maybe I am doing something wrong.

selectors/accessTokens.js

import { createSelector } from 'reselect'

import * as entities from './entities'

const ids = state => {
  return state.accessTokensList.get('accessTokenIds')
}

export const fetching = (state) => {
  return state.accessTokensList.get('fetching')
}

export const listEntities = createSelector(
  ids, entities.accessTokens,
  (ids, entities) => ids.map(id => entities.get(`${id}`))
)
...

selectors/entities.js

import { Map } from 'immutable'

export const ticketingSystems = state => state.entities.get('ticketingSystems') || Map()
export const tickets = state => state.entities.get('tickets') || Map()
export const ticketFieldValues = state => state.entities.get('ticketFieldValues') || Map()
...

selectors/audits.js

import { createSelector } from 'reselect'

import * as entities from './entities'

const ids = state => {
  return state.auditsList.get('auditIds')
}

export const fetching = (state) => {
  return state.auditsList.get('fetching')
}

export const listEntities = createSelector(
  ids, entities.audits,
  (ids, entities) => ids.map(id => entities.get(`${id}`))
)

export const selectedAuditId = (state) => {
  return state.auditsList.get('selectedAuditId') || null
}

export const selectedAudit = createSelector(
  selectedAuditId, entities.audits,
  (id, entities) => entities.get(`${id}`)
)
...

...

selectors/index.js

import * as accessTokens from './accessTokens'
import * as audits from './audits'
import * as ticketStatuses from './ticketStatuses'
import * as entities from './entities'

export {
  accessTokens,
  audits,
  ticketStatuses,
  entities
}

store.js

import { createStore, applyMiddleware, compose } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createSagaMiddleware from 'redux-saga'
import createRootReducer from 'reducers/rootReducer'
import { appHistory } from 'appHistory'
import root from 'sagas/root'

import * as selectors from 'selectors'
import * as ReselectTools from 'reselect-tools'

const connectedRootReducer = createRootReducer(appHistory)
const initialState = {}
const sagaMiddleware = createSagaMiddleware()

const middleware = [
  routerMiddleware(appHistory),
  sagaMiddleware
]

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
const enhancer = composeEnhancers(
  applyMiddleware(...middleware)
)

const store = createStore(connectedRootReducer, initialState, enhancer)
sagaMiddleware.run(root)

ReselectTools.getStateWith(() => store.getState())
ReselectTools.registerSelectors(selectors.accessTokens)
ReselectTools.registerSelectors(selectors.audits)
ReselectTools.registerSelectors(selectors.ticketStatuses)
ReselectTools.registerSelectors(selectors.entities)

export { store }

I think there should be a way to add selectors specifying the name it their groups like 'audits', 'entities' etc. In the code they are imported as

import * as auditsSelector from 'selectors/audits'

so it will be clear for developers.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.