Product
Socket Now Supports uv.lock Files
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
redux-collector
Advanced tools
Easy Collection Reducers for Redux.
http://redux-collector.mediadrake.com/
% npm install redux-collector
Redux Collector is a Reducer Generator for Collections
import { createStore } from 'redux';
import collectify from 'redux-collector';
function counterReducer(state = 0, action) {
switch(action.type) {
case 'INCREMENT':
return state + 1;
case 'DECRAMENT':
return state - 1;
default:
return state;
}
}
export default createStore(
collectify(
{
add: 'ADD_ITEM',
hydrate: 'HYDRATE_ITEMS',
remove: 'REMOVE_ITEM'
},
counterReducer
)
);
Then call actions using the types you created with collectify.
import store from './MyStore'
const {dispatch, subscribe, getState} = store;
subscribe(val => console.log('State is:', getState()));
dispatch({
type: 'HYDRATE_ITEMS',
data: [1, 2, 3, 4]
});
// State is : [1, 2, 3, 4]
dispatch({
type: 'ADD_ITEM',
data: 5
});
// State is : [1, 2, 3, 4, 5]
dispatch({
type: 'INCREMENT'
});
// State is : [2, 3, 4, 5, 6]
dispatch({
type: 'DECREMENT',
query: item => item >= 5
});
// State is : [1, 2, 3, 3, 4]
dispatch({
type: 'REMOVE',
query: item => item < 3
});
// State is : [3, 3, 4]
dispatch({
type: 'REMOVE',
limit: 2,
order: -1
});
// State is : [3]
The above reducers can actually be a whole lot shorter.
import { createStore } from 'redux';
import collectify from 'redux-collector';
export default createStore(
collectify(
{
add: 'ADD_ITEM',
hydrate: 'HYDRATE_ITEMS',
remove: 'REMOVE_ITEM'
},
{
defaultsTo: 0,
"INCREMENT": state => state + 1,
"DECRAMENT": state => state - 1
}
)
);
This is courtesy of Reducify. Head over to their documentation and check out all the ways you can make reducers. Any argument you pass to collectify gets automatically passed to reducify.
Basically, you'll get a reducer that does everything you need to arrays. When you call collectify
you'll get a chance to declare action types for any of the following methods.
collectify([actionTypes<Object>, [itemReducer<Object|Function>, collectionDefault<Array>]]);
One primary function, wrap your item reducers with this.
itemReducer
Use reducer that you want applied to items in an array as the first (optional) argument.
actionTypes
Use this to declare the action types you want to use to manipulate your array. This is a sample configuration:
function itemReducer(state, action) {
// reducer stuff
}
const actionTypes = {
add = 'ADD_MY_ITEM',
move = 'MOVE_MY_ITEM',
swap = 'SWAP_MY_ITEMS',
addRange = 'ADD_MY_ITEMS',
remove = 'REMOVE_MY_ITEMS',
hydrate = 'HYDRATE_MY_ITEMS',
sort = 'SORT_MY_ITEMS'
};
const myReducer = collectify(actionTypes, itemReducer);
If you pass only one argument, we assume the reducer is a static reducer for an empty array and will treat the one argument as the actionTypes
argument. See Reducify static reducers.
If your Collectify reducer does not receive an action type outlined in your config, any actions you dispatch will be passed to your item reducer for every item in your array.
For example:
function itemReducer(state, action) {
switch(action.type) {
case "INCREMENT_BY_TWO":
return state + 2;
default:
return state;
}
}
const myStore = createStore(collectify({hydrate: 'SET_ITEMS'}, itemReducer));
myStore.dispatch({
type: 'SET_ITEMS',
data: [2, 4, 6]
});
// state is [2, 4, 6]
myStore.dispatch({
type: 'INCREMENT_BY_TWO'
});
// state is [4, 6, 8]
Notice the action 'INCREMENT_BY_TWO' was applied to every item in the array.
There are two configuration options you can pass in with your action types.
This is the default or initial value of the collection.
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(collectify({}, itemReducer, [1, 2]));
myStore.dispatch({
type: 'PASS'
});
// state is [1, 2]
The default value for any item added with add
or addRange
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(collectify({itemDefault: 10, add: 'ADD_ITEM'}, itemReducer));
myStore.dispatch({
type: 'ADD_ITEM'
});
// state is [10]
The actions will manipulate your array in some form or another.
This sets the data for your collection. It will override your entire array unless you pass in a skip
parameter.
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(
collectify(
{
hydrate: 'HYDRATE_ITEMS'
},
itemReducer
)
);
myStore.dispatch({
type: 'HYDRATE',
data: [1, 2, 3, 4] // data is required for HYDRATE
});
This will add an item to your collection. It will default to itemDefault
if you passed one with your configuration.
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(
collectify(
{
itemDefault: 10,
add: 'ADD_ITEM'
},
itemReducer
)
);
myStore.dispatch({
type: 'ADD_ITEM'
});
// state is [10]
myStore.dispatch({
type: 'ADD_ITEM',
data: 11
});
// state is [10, 11]
You can pass any index argument as well.
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(
collectify(
{ add: 'ADD_ITEM' },
itemReducer
)
);
myStore.dispatch({
type: 'ADD_ITEM',
index: 0,
data: 2
});
// state is [2]
myStore.dispatch({
type: 'ADD_ITEM',
index: 0,
data: 3
});
// state is [3, 2]
The same as add
, except this is for adding multiple items at once.
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(
collectify(
{
itemDefault: 10,
addRange: 'ADD_MULTIPLE_ITEMS'
},
itemReducer
)
);
myStore.dispatch({
type: 'ADD_MULTIPLE_ITEMS'
});
// state is [10]
myStore.dispatch({
type: 'ADD_MULTIPLE_ITEMS',
data: [11, 12]
});
// state is [10, 11, 12]
You can pass any index argument as well.
function itemReducer(state, action) {
// reducer stuff
}
const myStore = createStore(
collectify(
{ addRange: 'ADD_MULTIPLE_ITEMS' },
itemReducer
)
);
myStore.dispatch({
type: 'ADD_MULTIPLE_ITEMS',
index: 0,
data: [2, 3]
});
// state is [2, 3]
myStore.dispatch({
type: 'ADD_MULTIPLE_ITEMS',
index: 0,
data: [4, 5]
});
// state is [4, 5, 2, 3]
This will remove items from your collection. If you do not pass a query, it will clear the entire collection.
const myStore = createStore(
collectify(
{
hydrate: 'HYDRATE_ITEMS',
remove: 'REMOVE_ITEMS'
},
[1, 2, 3, 4] // passing in an object and not a function will make this the default value
)
);
myStore.dispatch({
type: 'REMOVE_ITEMS'
});
// state is []
myStore.dispatch({
type: 'HYDRATE_ITEMS',
data: [1, 2, 3, 4]
});
// state is [1, 2, 3, 4]
myStore.dispatch({
type: 'REMOVE_ITEMS',
query: item => item > 2
});
// state is [1, 2]
myStore.dispatch({
type: 'REMOVE_ITEMS',
query: item => item > 2
});
// state is [1, 2]
myStore.dispatch({
type: 'REMOVE_ITEMS',
limit: 1
});
// state is [2]
You can pass any index argument as well.
myStore.dispatch({
type: 'HYDRATE_ITEMS',
data: [1, 2, 3, 4]
});
myStore.dispatch({
type: 'REMOVE_ITEMS',
index: 0
});
// state is [2, 3, 4]
myStore.dispatch({
type: 'REMOVE_ITEMS',
indexes: [0, 2]
});
// state is [3]
Using the same arguments for sort, you can modify the output of your list.
// same as above
const myColReducer = collectify(
{sort: 'SORT'},
[{ord: 1, value: 3}, {ord: 3, value: 5}, {ord: 2, value: 10}]
);
dispatch({
type: 'SORT',
sort: 'ord'
})
// result would be [{ord: 1, value: 3}, {ord: 2, value: 10}, {ord: 3, value: 5}]
Move lets you take a set of indexes
passed to a from
argument and places them at a single indexes provided by a to
argument.
const myColReducer = collectify(
{move: 'MOVE', hydrate: 'HYDRATE'},
[1, 2, 3, 4, 5]
);
dispatch({
type: 'MOVE',
from: 1,
to: 0
})
// result would be [2, 1, 3, 4, 5]
dispatch({
type: 'HYDRATE',
data: [1, 2, 3, 4, 5]
})
dispatch({
type: 'MOVE',
from: [1, 3], // treated as a list of indexes
to: 0
})
// result would be [2, 4, 1, 3, 5]
dispatch({
type: 'HYDRATE',
data: [1, 2, 3, 4, 5]
})
dispatch({
type: 'MOVE',
from: item => item === 3, // treated as a query
to: item => item === 2 // treated as a query, we will only use the first index returned
})
// result would be [1, 3, 2, 4, 5]
dispatch({
type: 'HYDRATE',
data: [1, 2, 3, 4, 5]
})
dispatch({
type: 'MOVE',
from: {after: item => item > 1},
to: 0
})
// result would be [2, 3, 4, 5, 1]
Swap switches item by index.
const myColReducer = collectify(
{move: 'SWAP'},
[1, 2, 3, 4, 5]
);
dispatch({
type: 'SWAP',
indexes: [1, 4]
})
// result would be [1, 5, 3, 4, 2]
While most swap
use cases should just take advantage of indexes
, you can swap more than one item and have everything move in a carousel.
const myColReducer = collectify(
{move: 'SWAP'},
[1, 2, 3, 4, 5]
);
dispatch({
type: 'SWAP',
after: item => item === 3
})
// result would be [1, 2, 5, 3, 4]
Check out operations for more details.
Because we may not always want to change the entire collection, Redux Collector looks for the keyword query
in your action that you dispatch. This query is processed by the collector matching method.
function increment(state = 0, action) {
switch(action.type) {
case 'INCREMENT':
return state + 1;
default:
return state;
}
}
const myStore = createStore(
collectify(
{
hydrate: 'HYDRATE_LIST'
},
increment
)
);
myStore.dispatch({
type: 'HYDRATE_LIST',
data: [-1, 2, 4]
});
// state is [-1, 2, 4]
myStore.dispatch({
type: 'INCREMENT',
query: function(item) {
return item > 0;
}
});
// all items are incremented by 1
// state is [-1, 3, 5]
By default, the matcher used by query
and after
inspects your query argument and compares it against every item in the collection.
By default, your query is processed as follows:
If undefined, we assume you want your operation applied to all items in the list.
If a function, we evaluate this function passing in [state, index]
. Returning true
will say that query matches the item. Returning false
will say it doesn't.
If you pass an array, we will process every item in the query with against every
(as an and, not or). For or, use $or. This is recursive.
If you pass an object with the key $or
, it must have a child that is an array and we will process this against some
. This is recursive.
myStore.dispatch({
type: 'INCREMENT',
query: [
item => item > 0,
item => item % 2 === 0
] // all items that are greater than 0 and are even
});
myStore.dispatch({
type: 'INCREMENT',
query: {
$or: [
item => item > 0,
item => item % 2 === 0
]
} // all items that are greater than 0 or are even
});
myStore.dispatch({
type: 'INCREMENT',
query: [
item => item % 2 === 0
{
$or: [
item => item > 0,
item => item === -2
]
}
] // all items that are even and are either greater than 0 or equal to -2
});
If true
, we assume you want your operation applied to all truthy items in the list.
If false
, we assume you want your operation applied to all falsy items in the list.
If you pass an object, we'll pass this to the lodash function _.isMatch
with the item's state as the first argument.
If you pass any number or string, we will do a strict equals comparison.
It's possible to customize your matcher function. Use the import {configureCollectify}
and pass in a matcher argument.
import {configureCollectify}
// We pass you the default matcher so you can add functionality without losing default matching behavior
function matcher(item, test, index, defaultMatcher) {
if (test === 'isOdd') {
return (item % 2) === 1;
}
return defaultMatcher(item, test, index);
}
const myCollectify = configureCollectify({matcher});
const myColReducer = myCollectify(
{},
incrementReducer,
[2, 3, 4]
);
dispatch({
type: 'INCREMENT',
query: 'isOdd'
})
// result is [2, 4, 4]
Beyond queries, there are other ways to apply your partial changes to your collection. These should be familiar to anyone with experience in mongo or other collection libraries.
Limits the number of operations your query will execute. This applies to all reducer behavior and remove
.
const myColReducer = collectify(
{},
incrementReducer,
[1, 2, 3]
);
dispatch({
type: 'INCREMENT',
limit: 1
})
// result would be [2, 2, 3]
const myColReducer = collectify(
{remove: 'REMOVE'},
[1, 2, 3]
);
dispatch({
type: 'REMOVE',
limit: 1
})
// result would be [2, 3]
Skips a number of items before executing your operation. This applies to all reducer behavior and remove
.
const myColReducer = collectify(
{},
incrementReducer,
[1, 2, 3]
);
dispatch({
type: 'INCREMENT',
skip: 1
})
// result would be [1, 3, 4]
Skip and limit can be combined together.
const myColReducer = collectify(
{},
incrementReducer,
[1, 2, 3]
);
dispatch({
type: 'INCREMENT',
skip: 1,
limit: 1
})
// result would be [1, 3, 3]
A shortcut for skip = index
and limit = 1
. Indexes are pythonic, meaning that -1 is equal to the last item in the array.
const myColReducer = collectify(
{},
incrementReducer,
[1, 2, 3]
);
dispatch({
type: 'INCREMENT',
index: 1
})
// result would be [1, 3, 3]
const myColReducer = collectify(
{},
incrementReducer,
[1, 2, 3]
);
dispatch({
type: 'INCREMENT',
index: -1
})
// result would be [1, 2, 4]
The same as index
, but should be an array of indexes. Like index
, these values are pythonic.
const myColReducer = collectify(
{remove: 'REMOVE'},
[1, 2, 3]
);
dispatch({
type: 'REMOVE',
indexes: [0, 2]
})
// result would be [2]
Should be a two dimensional array of pythonic indexes of the form [start, end]
.
const myColReducer = collectify(
{remove: 'REMOVE'},
[1, 2, 3, 4]
);
dispatch({
type: 'REMOVE',
range: [0, 2]
})
// result would be [4]
Should be a query
that, upon first match, will determine the skip
and limit
.
const myColReducer = collectify(
{remove: 'REMOVE'},
[1, 2, 3, 4]
);
dispatch({
type: 'REMOVE',
after: (arg, index) => arg === 2
})
// result would be [1, 2]
Keep in mind, this is only evaluated once. If the after function returns true, the rest of the collection will have operations applied to it.
Sorts the collection before applying operations to it. This uses the same arguments available for sort
, but does not actually sort your list. This is useful when used in conjunction with any index operators.
function incrementReducer(state = {value: 0}, action) {
switch(action.type) {
case 'INCREMENT':
return {...state, value: state.value + 1};
default:
return state;
}
}
const myColReducer = collectify(
{},
incrementReducer,
[{ord: 1, value: 3}, {ord: 3, value: 5}, {ord: 2, value: 10}]
);
dispatch({
type: 'INCREMENT',
sort: 'ord',
limit : 2
})
// result would be [{ord: 1, value: 4}, {ord: 3, value: 5}, {ord: 2, value: 11}]
// notice the second element was not changed
const myColReducer = collectify(
{remove: 'REMOVE'},
[{ord: 1, value: 3}, {ord: 3, value: 5}, {ord: 2, value: 10}]
);
dispatch({
type: 'REMOVE',
sort: 'ord',
limit : 1,
skip: 1
})
// result would be [{ord: 1, value: 3}, {ord: 3, value: 5}]
You can pass a function to sort
if you need to do something more complicated than just checking against a single key. Keep in mind, that order
can still flip your collection. If you sort by the negative result of a key, your order
argument might undo that.
// same as above
const myColReducer = collectify(
{remove: 'REMOVE'},
[{ord: 1, value: 3}, {ord: 3, value: 5}, {ord: 2, value: 10}]
);
dispatch({
type: 'REMOVE',
sort: item => item.ord,
limit : 1,
skip: 1
})
// result would be [{ord: 1, value: 3}, {ord: 3, value: 5}]
This determines the direction your collections's sorting occurs in. If you do not sort your list, it can be used to just reverse the direction your operations are done in.
const myColReducer = collectify(
{remove: 'REMOVE'},
[1, 5, 2]
);
// removes the last item in the list
dispatch({
type: 'REMOVE',
order: -1,
limit : 1
})
// result would be [1, 5]
const myColReducer = collectify(
{remove: 'REMOVE'},
[{ord: 1, value: 3}, {ord: 3, value: 5}, {ord: 2, value: 10}]
);
dispatch({
type: 'REMOVE',
sort: 'ord',
order : -1
})
// result would be [{ord: 1, value: 3}, {ord: 3, value: 5}]
You can combine order
and sort
with a shortcut by passing an object to sort
.
// same as above
const myColReducer = collectify(
{remove: 'REMOVE'},
[{ord: 1, value: 3}, {ord: 3, value: 5}, {ord: 2, value: 10}]
);
dispatch({
type: 'REMOVE',
sort: {ord: -1}
})
// result would be [{ord: 1, value: 3}, {ord: 3, value: 5}]
Not every reducer is just a single array. Sometimes your array might be a property on the reducer itself. There's two ways to solve this.
The easy way.
Redux Pipeline can you let select object properties to apply certain reducers to.
It would look something like this:
const myState = {items: []};
function itemReducer(state = 0, action) {
// reducer
}
const myReducer = pipeline(
myState,
{
$: 'items',
reducer: collectify({
add: 'ADD',
// etc.
}, itemReducer)
}
);
The slightly more difficult, but not impossible, way.
Track all actions that would apply to your collection and run them through a collectified reducer for that item. It would look like this:
const collectified = collectify({
add: "ADD"
}, {
"INCREMENT": state => state + 1
});
function reducer(state = {items: []}, action = {}) {
switch(action.type) {
case 'ADD':
case 'INCREMENT':
return {...state, items: collectified(state.items, action)};
default:
return state;
}
}
const store = createStore(reducer);
const {dispatch, getState} = store;
dispatch({type: 'ADD', data: 10});
// state is {items: [10]}
dispatch({type: 'INCREMENT'});
// state is {items: [11]}
A lot of operations will override behavior of other operations. Mixing them together can lead to odd consequences. We try our best to merge them all, but, when in doubt, either use specific indexes or stick to skip
and limit
.
const myColReducer = collectify(
{},
incrementReducer,
[1, 2, 3, 4]
);
dispatch({
type: 'INCREMENT',
skip: 1,
index: 0,
limit 2
})
// result would be [1, 3, 4]
// index just gets ignored
Redux Collector uses Reducify for your reducer config. This means you can pass any reducer function or a configuration. However, Redux Collector only works on collections, so trying to select something with your configuration will throw an error.
const myState = {items: []};
const myReducer = collectify(
{},
{
$: 'items',
"INCREMENT": state => state + 1
},
myState
);
// throws an error, use pipeline instead
If you find yourself selecting something within collectify
, refer to the With Pipeline section of this readme.
Redux Collector is free software under the MIT license. It was created in sunny Santa Monica by Matthew Drake.
FAQs
Easy Collection Reducers for Redux
The npm package redux-collector receives a total of 1 weekly downloads. As such, redux-collector popularity was classified as not popular.
We found that redux-collector demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Product
Socket now supports uv.lock files to ensure consistent, secure dependency resolution for Python projects and enhance supply chain security.
Research
Security News
Socket researchers have discovered multiple malicious npm packages targeting Solana private keys, abusing Gmail to exfiltrate the data and drain Solana wallets.
Security News
PEP 770 proposes adding SBOM support to Python packages to improve transparency and catch hidden non-Python dependencies that security tools often miss.