Coder Social home page Coder Social logo

reselect-map's Issues

Including the key in the mapper function

Great utility library! Ran into this exact scenario and was about to implement it myself until I stumbled across this.

One thing that would be great would be to add support for getting the key in the mapper function in createObjectSelector. For instance I might have an example like:

import { createObjectSelector } from 'reselect-map'

const exampleState = {
  numbers: { a: 1, b: 2 },
}

const mul = createObjectSelector(
  state => state.numbers,
  state => state.multiplier,
  (number, key, multiplier) => `${key}-${number * multiplier}`
)

console.log(mul(exampleState)) // { a: 'a-5', b: 'b-10' }

Lodash and other utility libraries typically provide the key also, typically as the second argument. Seems like it would be easy to change https://github.com/HeyImAlex/reselect-map/blob/master/src/memoize.js#L80 from

const result = fn(value, ...args)

to

const result = fn(value, key, ...args)

Obviously this is a breaking change and not every mapper would necessarily need the key, so thought I'd see what approach you'd take before submitting a PR

Customizing the selector created for each element

Imagine each element of your array or collection is a deep object, say a big fat graphql result:

[
  {
    id: 1,
    name: 'Andy',
    orders: [
      {
        orderNumber: '23152',
        date: '2018-01-05',
        items: [
          {
            id: '182341',
            name: 'Fog Machine',
          },
          {
            id: '198234',
            name: 'Strobe Light',
          }
        ]
      }
    ]
  },
  ...
]

And you want to create an efficient selector for each element that picks out the user's name and the first item of the first order:

function createSelectorForElement(key) {
  return createSelector(
    user => user.name,
    createSelector(
      user => user.orders[0],
      (order = {items :[]}) => order.items[0],
      item => item & item.name
    ),
    (name, firstItem) => ({name, firstItem})
  )
}

Doesn't seem like there's currently a way to do this with reselect map? It would be nice to be able to pass a createSelectorForElement option.

Complex transformations

I currently have a reducer which looks something like the following:

const initialState = { 
    listing: [],
    byId: {},
    byContextId: {},
    filteredIds: [],
    filters: {},
    selectedContextId: null,
    selectedContext: null
}

export default function messages(state = initialState, action) {
    if (action.type == MESSAGES_RECEIVED_BATCH || action.type == MESSAGES_RECEIVED_ITEM) { 
        console.time(`[PERF] ${MESSAGES_RECEIVED_BATCH}`);

        let listing = state.listing;
        let byId = state.byId;
        let byContextId = state.byContextId;

        // sometimes I can get messages sent which I already have
        const newListing = [];
        action.messages.forEach(message => {
            if (!state.byId[message.id]) {
                newListing.push(message);
            }
        });

        if (newListing.length > 0) {
            // - index messages by id
            // - group messages by context id (with nested list and sub grouping by type)
            // - store messages in array

            let newById = {};
            let newByContextId = {};
            newListing.forEach(message => {
                const context = newByContextId[message.context.id] || { listing: [], byType: {} };
                message.types.forEach(type => {
                    context.byType[type] = context.byType[type] || [];
                    context.byType[type].push(message);
                });
                context.listing.push(message);

                newByContextId[message.context.id] = context;
                newById[message.id] = message;
            });
            for (var contextId in byContextId) {
                const context = byContextId[contextId];
                const newContext = newByContextId[contextId];
                if (newContext) {
                    newContext.listing.push(...context.listing);
                    Object.assign(newContext.byType, context.byType);
                }
                else {
                    newByContextId[contextId] = context;
                }
            }
            byContextId = newByContextId;
            byId = Object.assign(newById, state.byId);

            newListing.push(listing);
            listing = newListing;

        }

        ....

        console.timeEnd(`[PERF] ${MESSAGES_RECEIVED_BATCH}`);

        return {
            ...state,
            listing,
            byId,
            byContextId,
            filteredIds,
            selectedContext
        };
    }
    else if (action.type == MESSAGES_REQUESTED_ITEM) {
        return {
            ...state,
            selectedContextId: action.contextId,
            selectedContext: state.byContextId[action.contextId]
        }
    }
    return state;
}

To help paint a picture of what the above is doing, the following is the type of data I have and the structures that I'm trying to have "access" to (note, it's more complex than this but trying to simply things):

// input
action.messages = [
    { id: 1, context: { id: 1, ... }, types: [ 'a', 'b' ], payload: { ... } }, 
    { id: 2, context: { id: 2, ... }, types: [ 'c' ], payload: { ... } }, 
    { id: 3, context: { id: 1, ... }, types: [ 'a' ], payload: { ... } }, 
    { id: 4, context: { id: 1, ... }, types: [ 'c' ], payload: { ... } }, 
    { id: 5, context: { id: 1, ... }, types: [ 'd' ], payload: { ... } }, 
    { id: 6, context: { id: 2, ... }, types: [ 'c' ], payload: { ... } }, 
    { id: 7, context: { id: 2, ... }, types: [ 'a' ], payload: { ... } }, 
];

// output
state = {
    listing: [ ... ], // Simple: Unique list of messages by message.id
    byId: { ... },   // Simple: Indexed messages by message.id
    byContextId: {
        1: {
            listing: [ ... ], // Unique list of messages by message.id where context.id = 1
            byType: {
                'a': [ .... ], // Unique list of messages by message.id where message.context.id = 1 and message.type = 'a'
                'b': [ .... ], // Unique list of messages by message.id where message.context.id = 1 and message.type = 'b'
                ...
            },
            ... // others here
        }
    }, // Harder: Grouped by message.context.id
    ...
}

As you can see there are some complex but not extraordinary stuff going on here. There is also a lot which shouldn't be here and should be in selector.

Now, given the relative complexity of the transformation and number of messages that are being dealt with (range 500-5000), I'm keen not to have to recalculate everything each time I get a new message come in (which I'm currently defending against and selectors don't do out of the box). In thinking about shifting this logic into selectors, I'm trying to figure out how I'm best to only recalculate what has actually been affected... hence how I came across reselect-map.

When looking at the API I'm left wondering if there is currently support for my needs? What I'm thinking I need is a way to describe how the data should be "keyed"... In my case I would say that the key for byContextId is at message.context.id... then reselect-map would only make me recalculate the state for the contextIds that where received in received batch of messages and remember the state of the rest.

Any thoughts on how I should do this or I'm thinking about this incorrectly, etc.

Customizing key used for memoization

The array/list selectors in this package have a problem: if you splice in some elements at index n, you lose all the memoization for everything that came after n.

Edit: now that I glanced at the code I see you have an optional undocumented equalityCheck argument. In a lot of cases it's probably more straightforward to just use a function that determines the cache key for a given element.

But often it's common to have an array of objects containing a property that can act as a key:

[
  {id: 1, name: 'Andy'},
  {id: 2, name: 'John'},
  {id: 3, name: 'Martha'},
  ...
]

In this case if I could just provide a function that tells createArraySelector to use element.id as the key, then the results for each id would stay memoized no matter how the array was shuffled.

One way to do this would be to provide a keyBy option with the signature (value, originalKey) => newKey:

createArraySelector(
  state => state.users,
  state => state.multiplier,
  (user, multiplier) => user.age * multiplier,
  {keyBy: user => user.id}
)

Running into issues trying out the examples

require("reselect/package.json"); // reselect is a peer dependency. 
var reselectMap = require("reselect-map")
var createArraySelector = reselectMap.createArraySelector;

const exampleState = {
  numbers: [1, 2.5, 3],
  multiplier: 5
};

const mul = createArraySelector(
  state => state.numbers,
  state => state.multiplier,
  (number, multiplier) => number * multiplier
);

const result = mul(exampleState);
console.log(result)

https://runkit.com/embed/lzksevx8x4ok

I ran the above, and continuously get an empty array. I also tried to do the same with createObjectSelector, but it was hitting some crashes. Any help on this would be great.

Thanks!

Add typescript type definition file

I am using your library from a typescript project.

Please could you add a typing definition to the project so that it can be used in a type safe way.
Have a look at the 'reselect' module because they have done it there and your module is very similar.

Thanks!

How does memoization work?

Hi there,

This looks very interesting for a use case that we have. But I have a question regarding memoization, because we're having an issue with the regular selectors from reselect.

Take this data set (it's a JSON API collection):

[
  { "type": "tasks", "id": "1", "attributes": { "name": "Do stuff" } },
  { "type": "tasks", "id": "2", "attributes": { "name": "Procrastinate" } }
]

We could create a selector with reselect, that would accept a function returning this array as the first argument and find a particular object in the callback:

const resourceSelector = createSelector(
  state => state.resourceCollection,
  resourceCollection => resourceCollection.find(res => res.id === '1')
);

The problem we have with this solution is that the memoization will not be optimized for our use case, as we would like for the selector to only run if the output actually changes.
Quick note: we build our own equalityCheck to check prev/next attributes for these particular objects.

I understand that this is due to the memoization looking at the input to the selectors, i.e. the collection of resources. So if another resource was added, it would not return the cached value and our component would re-render.

Would I be correct in assuming that this could be one of the use cases for reselect-map?

Our current workaround is simply to do this:

const giveMeResource = createSelector(
  resourceSelector,
  resource => resource
);

That's stupid ๐Ÿ˜ž

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.