First, here's my HOC:
export default function connectField({
nameProp = 'name',
valueProp = 'value',
dispatchProp = 'dispatch'
}: ConnectOptions) {
return compose(
getContext(contextTypes),
connect((state, ownProps) => {
const path = [namespace,...getPath(ownProps),...toPath(ownProps[nameProp])];
const value = getOr('', path, state);
return {
[valueProp]: value
};
}, (dispatch,ownProps) => { // <----------- mapDispatchToProps
const path = [...getPath(ownProps),...toPath(ownProps[nameProp])];
return {
[dispatchProp]: value => dispatch({type: ActionTypes.Change, payload: {path, value}})
};
}, (stateProps, dispatchProps, {[FIELD_PATH]: _, ...ownProps}) => {
return {...stateProps, ...dispatchProps, ...ownProps};
}, {
areMergedPropsEqual: (a,b) => {
let eq = shallowEqual(a,b);
console.log('areMergedPropsEqual',a,b,eq);
return eq;
},
}),
withContext(contextTypes, props => {
return {[FIELD_PATH]: [...getPath(props), props[nameProp]]};
}),
);
}
In the middle there is my mapDispatchToProps. That's causing areMergedPropsEqual to return false every time because it's creating a new action creator every time.
I can't figure out how to memoize this bit:
value => dispatch({type: ActionTypes.Change, payload: {path, value}})
Such that I get back the same function instance every time.
There's some notes in the docs about "per-instance memoization" which is what I want, but I can't quite make heads or tails of what I'm supposed to do here.
To be clear, I know how to memoize a function. However, I don't want a use a big cache with infinite history. It's unnecessary memory consumption. I just need a cache size of 1 like how reselect does it. The problem is that I can't create the "selector" directly inside connectField because that still creates a single shared instance -- i.e., all "connected fields" will share the same cache and they'll overwrite each other, negating the benefit. It has to be per component instance. This is specific to React-Redux's connect method. There's a syntax for it so that you can create your selector at the right spot, and it will only get ran once per instance. I'm just having trouble deciphering the API -- do they expect a function that returns a function that returns an object? Or an object with propnames as keys and functions as values? What does that function return? i.e., the docs aren't clear about all the different variations that are accepted for the mapDispatchToProps option.
If you already have lodash, you have a memoize function that allow to transform any function into a memoized function. This memoized function will calculate the return value for a given parameter and will then always return this same return value each you supply the same parameter.
You can use it like this for example :
import { memoize } from 'lodash'
const makeChangeDispatcher = memoize( (dispatch, path) =>
value => dispatch({type: ActionTypes.Change, payload: {path, value}})
)
...
(dispatch,ownProps) => { // <----------- mapDispatchToProps
const path = [...getPath(ownProps),...toPath(ownProps[nameProp])];
return {
[dispatchProp]: makeChangeDispatcher(dispatch, path)
};
}
...
You can see more infos on the lodash documentation of memoize
Related
I've been following along the REDUX essentials guide and I'm at part 8, combining RTK Query with the createEntityAdapter. I'm using the guide to implement it in a personal project where my getUni endpoint has an argument named country, as you can see from the code snippet below.
I'm wondering is there anyway to access the country argument value from the state in universityAdaptor.getSelector(state => ) at the bottom of the snippet, as the query key name keeps changing.
import {
createEntityAdapter,
createSelector,
nanoid
} from "#reduxjs/toolkit";
import {
apiSlice
} from "../api/apiSlice";
const universityAdapter = createEntityAdapter({})
const initialState = universityAdapter.getInitialState();
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUni: builder.query({
query: country => ({
url: `http://universities.hipolabs.com/search?country=${country}`,
}),
transformResponse: responseData => {
let resConvert = responseData.slice()
.sort((a, b) => a.name.localeCompare(b.name))
.map(each => {
return { ...each,
id: nanoid()
}
});
return universityAdapter.setAll(initialState, resConvert)
}
})
})
});
export const {
useGetUniQuery
} = extendedApiSlice;
export const {
selectAll: getAllUniversity
} = universityAdapter.getSelectors(state => {
return Object.keys({ ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }).length === 0
? initialState : { ...state.api.queries[<DYNAMIC_QUERY_NAME>]data }
})
UPDATE: I got it working with a turnery operator due to the multiple redux Actions created when RTK Query handles fetching. Wondering if this is best practice as I still haven't figured out how to access the country argument.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
return !Object.values(state.api.queries)[0]
? initialState : Object.values(state.api.queries)[0].status !== 'fulfilled'
? initialState : Object.values(state.api.queries)[0].data
})
I wrote that "Essentials" tutorial :)
I'm actually a bit confused what your question is - can you clarify what specifically you're trying to do?
That said, I'll try to offer some hopefully relevant info.
First, you don't need to manually call someEndpoint.select() most of the time - instead, call const { data } = useGetThingQuery("someArg"), and RTKQ will fetch and return it. You only need to call someEndpoint.select() if you're manually constructing a selector for use elsewhere.
Second, if you are manually trying to construct a selector, keep in mind that the point of someEndpoint.select() is to construct "a selector that gives you back the entire cache entry for that cache key". What you usually want from that cache entry is just the received value, which is stored as cacheEntry.data, and in this case that will contain the normalized { ids : [], entities: {} } lookup table you returned from transformResponse().
Notionally, you might be able to do something like this:
const selectNormalizedPokemonData = someApi.endpoints.getAllPokemon.select();
// These selectors expect the entity state as an arg,
// not the entire Redux root state:
// https://redux-toolkit.js.org/api/createEntityAdapter#selector-functions
const localizedPokemonSelectors = pokemonAdapter.getSelectors();
const selectPokemonEntryById = createSelector(
selectNormalizedPokemonData ,
(state, pokemonId) => pokemonId,
(pokemonData, pokemonId) => {
return localizedPokemonSelectors.selectById(pokemonData, pokemonId);
}
)
Some more info that may help see what's happening with the code in the Essentials tutorial, background - getLists endpoint takes 1 parameter, select in the service:
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId) : [];
};
And my selector in the slice:
export const selectAllLists = createSelector(getListsResult, (listsResult) => {
console.log('inside of selectAllLists selector = ', listsResult);
return listsResult.data;
// return useSelector(listsResult) ?? [];
});
Now this console logs listsResult as ƒ memoized() { function! Not something that can have .data property as tutorial suggests. Additionally return useSelector(listsResult) - makes it work, by executing the memoized function.
This is how far I got, but from what I understand, the code in the Essentials tutorial does not work as it is...
However going here https://codesandbox.io/s/distracted-chandrasekhar-r4mcn1?file=/src/features/users/usersSlice.js and adding same console log:
const selectUsersData = createSelector(selectUsersResult, (usersResult) => {
console.log("usersResult", usersResult);
return usersResult.data;
});
Shows it is not returning a memorised function, but an object with data on it instead.
Wonder if the difference happening because I have a parameter on my endpoint...
select returns a memoized curry function. Thus, call it with first with corresponding arg aka tribeId in your case and then with state. This will give you the result object back for corresponding chained selectors.
export const getListsResult = (state: RootState) => {
return state.tribeId ? extendedApi.endpoints.getLists.select(state.tribeId)(state) : [];
};
The intention of the getUni endpoint was to produce an array of university data. To implement the .getSelector function to retrieve that array, I looped over all query values, searching for a getUni query and ensuring they were fulfilled. The bottom turnery operator confirms the getUni endpoint was fired at least once otherwise, it returns the initialState value.
export const { selectAll: getAllUniversity } = universityAdapter
.getSelectors(state => {
let newObj = {};
for (const value of Object.values(state.api.queries)) {
if (value?.endpointName === 'getUni' && value?.status === 'fulfilled') {
newObj = value.data;
}
}
return !Object.values(newObj)[0] ? initialState : newObj;
})
I created a hook to use a confirm dialog, this hook provides the properties to the component to use them like this:
const { setIsDialogOpen, dialogProps } = useConfirmDialog({
title: "Are you sure you want to delete this group?",
text: "This process is not reversible.",
buttons: {
confirm: {
onPress: onDeleteGroup,
},
},
width: "360px",
});
<ConfirmDialog {...dialogProps} />
This works fine, but also I want to give the option to change these properties whenever is needed without declaring extra states in the component where is used and in order to achieve this what I did was to save these properties in a state inside the hook and this way provide another function to change them if needed before showing the dialog:
interface IState {
isDialogOpen: boolean;
dialogProps: TDialogProps;
}
export const useConfirmDialog = (props?: TDialogProps) => {
const [state, setState] = useState<IState>({
isDialogOpen: false,
dialogProps: {
...props,
},
});
const setIsDialogOpen = (isOpen = true) => {
setState((prevState) => ({
...prevState,
isDialogOpen: isOpen,
}));
};
// Change dialog props optionally before showing it
const showConfirmDialog = (dialogProps?: TDialogProps) => {
if (dialogProps) {
const updatedProps = { ...state.dialogProps, ...dialogProps };
setState((prevState) => ({
...prevState,
dialogProps: updatedProps,
}));
}
setIsDialogOpen(true);
};
return {
setIsDialogOpen,
showConfirmDialog,
dialogProps: {
isOpen: state.isDialogOpen,
onClose: () => setIsDialogOpen(false),
...state.dialogProps,
},
};
};
But the problem here is the following:
Arguments are passed by reference so if I pass a function to the button (i.e onDeleteGroup) i will keep the function updated to its latest state to perform the correct deletion if a group id changes inside of it.
But as I'm saving the properties inside a state the reference is lost and now I only have the function with the state which it was declared at the beginning.
I tried to add an useEffect to update the hook state when arguments change but this is causing an infinite re render:
useEffect(() => {
setState((prevState) => ({
...prevState,
dialogProps: props || {},
}));
}, [props]);
I know I can call showConfirmDialog and pass the function to update the state with the latest function state but I'm looking for a way to just call the hook, declare the props and not touch the dialog props if isn't needed.
Any answer is welcome, thank you for reading.
You should really consider not doing this, this is not a good coding pattern, this unnecessarily complicates your hook and can cause hard to debug problems. Also this goes against the "single source of truth" principle. I mean a situation like the following
const Component = ({title}: {title?: string}) => {
const {showConfirmDialog} = useConfirmDialog({
title,
// ...
})
useEffect(() => {
// Here you expect the title to be "title"
if(something) showConfirmDialog()
}, [])
useEffect(() => {
// Here you expect the title to be "Foo bar?"
if(somethingElse) showConfirmDialog({title: 'Foo bar?'})
}, [])
// But if the second dialog is opened, then the first, the title will be
// "Foo bar?" in both cases
}
So please think twice before implementing this, sometimes it's better to write a little more code but it will save you a lot debugging.
As for the answer, I would store the props in a ref and update them on every render somehow like this
/** Assign properties from obj2 to obj1 that are not already equal */
const assignChanged = <T extends Record<string, unknown>>(obj1: T, obj2: Partial<T>, deleteExcess = true): T => {
if(obj1 === obj2) return obj1
const result = {...obj1}
Object.keys(obj2).forEach(key => {
if(obj1[key] !== obj2[key]) {
result[key] = obj2[key]
}
})
if(deleteExcess) {
// Remove properties that are not present on obj2 but present on obj1
Object.keys(obj1).forEach(key => {
if(!obj2.hasOwnProperty(key)) delete result[key]
})
}
return result
}
const useConfirmDialog = (props) => {
const localProps = useRef(props)
localProps.current = assignChanged(localProps.current, props)
const showConfirmDialog = (changedProps?: Partial<TDialogProps>) => {
localProps.current = assignChanged(localProps.current, changedProps, false)
// ...
}
// ...
}
This is in case you have some optional properties in TDialogProps and you want to accept Partial properties in showConfirmDialog. If this is not the case, you could simplify the logic a little by removing this deleteExcess part.
You see that it greatly complicates your code, and adds a performance overhead (although it's insignificant, considering you only have 4-5 fields in your dialog props), so I really recommend against doing this and just letting the caller of useConfirmDialog have its own state that it can change. Or maybe you could remove props from useConfirmDialog in the first place and force the user to always pass them to showConfirmDialog, although in this case this hook becomes kinda useless. Maybe you don't need this hook at all, if it only contains the logic that you have actually shown in the answer? It seems like pretty much the only thing it does is setting isDialogOpen to true/false. Whatever, it's your choice, but I think it's not the best idea
I'm using custom hooks for a component, and the custom hook uses a custom context. Consider
/* assume FooContext has { state: FooState, dispatch: () => any } */
const useFoo = () => {
const { state, dispatch } = useContext(FooContextContext)
return {apiCallable : () => apiCall(state) }
}
const Foo = () => {
const { apiCallable } = useFoo()
return (
<Button onClick={apiCallable}/>
)
}
Lots of components will be making changes to FooState from other components (form inputs, etc.). It looks to me like Foo uses useFoo, which uses state from FooStateContext. Does this mean every change to FooContext will re-render the Foo component? It only needs to make use of state when someone clicks the button but never otherwise. Seems wasteful.
I was thinking useCallback is specifically for this, so I am thinking return {apiCallable : useCallback(() => apiCall(state)) } but then I need to add [state] as a second param of useCallback. Then that means the callback will be re-rendered whenever state updates, so I'm back at the same issue, right?
This is my first time doing custom hooks like this. Having real difficulty understanding useCallback. How do I accomplish what I want?
Edit Put another way, I have lots of components that will dispatch small changes to deeply nested properties of this state, but this particular component must send the entire state object via a RESTful API, but otherwise will never use the state. It's irrelevant for rendering this component completely. I want to make it so this component never renders even when I'm making changes constantly to the state via keypresses on inputs (for example).
Since you provided Typescript types in your question, I will use them in my response.
Way One: Split Your Context
Given a context of the following type:
type ItemContext = {
items: Item[];
addItem: (item: Item) => void;
removeItem: (index: number) => void;
}
You could split the context into two separate contexts with the following types:
type ItemContext = Item[];
type ItemActionContext = {
addItem: (item: Item) => void;
removeItem: (index: number) => void;
}
The providing component would then handle the interaction between these two contexts:
const ItemContextProvider = () => {
const [items, setItems] = useState([]);
const actions = useMemo(() => {
return {
addItem: (item: Item) => {
setItems(currentItems => [...currentItems, item]);
},
removeItem: (index: number) => {
setItems(currentItems => currentItems.filter((item, i) => index === i));
}
};
}, [setItems]);
return (
<ItemActionContext.Provider value={actions}>
<ItemContext.Provider value={items}>
{children}
</ItemContext.Provider>
</ItemActionContext.Provider>
)
};
This would allow you to get access to two different contexts that are part of one larger combined context.
The base ItemContext would update as items are added and removed causing rerenders for anything that was consuming it.
The assoicated ItemActionContext would never update (setState functions do not change for their lifetime) and would never directly cause a rerender for a consuming component.
Way Two: Some Version of an Subscription Based Value
If you make the value of your context never change (mutate instead of replace, HAS THE WORLD GONE CRAZY?!) you can set up a simple object that holds the data you need access to and minimises rerenders, kind of like a poor mans Redux (maybe it's just time to use Redux?).
If you make a class similar to the following:
type Subscription<T> = (val: T) => void;
type Unsubscribe = () => void;
class SubscribableValue<T> {
private subscriptions: Subscription<T>[] = [];
private value: T;
constructor(val: T) {
this.value = val;
this.get = this.get.bind(this);
this.set = this.set.bind(this);
this.subscribe = this.subscribe.bind(this);
}
public get(): T {
return this._val;
}
public set(val: T) {
if (this.value !== val) {
this.value = val;
this.subscriptions.forEach(s => {
s(val)
});
}
}
public subscribe(subscription: Subscription<T>): Unsubscriber {
this.subscriptions.push(subscription);
return () => {
this.subscriptions = this.subscriptions.filter(s => s !== subscription);
};
}
}
A context of the following type could then be created:
type ItemContext = SubscribableValue<Item[]>;
The providing component would look something similar to:
const ItemContextProvider = () => {
const subscribableValue = useMemo(() => new SubscribableValue<Item[]>([]), []);
return (
<ItemContext.Provider value={subscribableValue}>
{children}
</ItemContext.Provider>
)
};
You could then use some a custom hooks to access the value as needed:
// Get access to actions to add or remove an item.
const useItemContextActions = () => {
const subscribableValue = useContext(ItemContext);
const addItem = (item: Item) => subscribableValue.set([...subscribableValue.get(), item]);
const removeItem = (index: number) => subscribableValue.set(subscribableValue.get().filter((item, i) => i === index));
return {
addItem,
removeItem
}
}
type Selector = (items: Item[]) => any;
// get access to data stored in the subscribable value.
// can provide a selector which will check if the value has change each "set"
// action before updating the state.
const useItemContextValue = (selector: Selector) => {
const subscribableValue = useContext(ItemContext);
const selectorRef = useRef(selector ?? (items: Item[]) => items)
const [value, setValue] = useState(selectorRef.current(subscribableValue.get()));
const useEffect(() => {
const unsubscribe = subscribableValue.subscribe(items => {
const newValue = selectorRef.current(items);
if (newValue !== value) {
setValue(newValue);
}
})
return () => {
unsubscribe();
};
}, [value, selectorRef, setValue]);
return value;
}
This would allow you to reduce rerenders using selector functions (like an extremely basic version of React Redux's useSelector) as the subscribable value (root object) would never change reference for its lifetime.
The downside of this is that you have to manage the subscriptions and always use the set function to update the held value to ensure that the subscriptions will be notified.
Conclusion:
There are probably a number of other ways that different people would attack this problem and you will have to find one that suits your exact issue.
There are third party libraries (like Redux) that could also help you with this if your context / state requirements have a larger scope.
Does this mean every change to FooContext will re-render the Foo component?
Currently (v17), there is no bailout for Context API. Check my another answer for examples. So yes, it will always rerender on context change.
It only needs to make use of state when someone clicks the button but never otherwise. Seems wasteful.
Can be fixed by splitting context providers, see the same answer above for explanation.
There is a board game that every time the user changes something in one square in it is affect the status of all other squares. To change a square's state I use useReducer, and to handle the change on the other squares I use useEffect.
The problem is when the user requests the computer to resolve by own the situation (there is such an option). My preferred solution is to trigger a recursive function (resolveBoard) that goes over all the squares and change them, one after one. The calculation on what to do in each square is based on the side effect of the former decision in the previous squares (what should be done by the useEffect mentioned above).
Unfortunately, the useReducer's dispatcher (and, as result, the useEffect) is be called only after the recursive function finished to go all over the board, which spoils the calculation and causes it returns an incorrect result.
How it can be done?
const [squaresValues, dispatchSquaresValue] = useReducer(
setSquaresValue,
{ rows: props.rows, columns: props.columns },
InilizeBoard
);
useEffect(() => {
calculateValues();
}, [squaresValues]);
function resolveBoard(row, index) {
...
if (resolveSingleSquare(row, index)) {
const nextIndex = ...
const nextRow = ....
return resolveBoard(nextRow, nextIndex);
}
return false;
}
function resolveSingleSquare(row, index) {
...calculations based on the state of the others squares
if (working) {
dispatchSquaresValue(squareProp);
return true;
}
return false;
}
function setSquaresValue(prevValues, squareProp) {
--- be called only after all finished:(
}
Ciao, in order to dispatch dispatchSquaresValue before re-call resolveBoard(nextRow, nextIndex); you have to use a Promise mechanism. So you cannot use useReducer. There are several ways to solve your problem, I used 2:
redux-promise:
export const updateSquareValue = (key, value)=>
Promise.resolve({
type:'UPDATE_SQUARE_VALUE',
key, value
})
Then in your component:
import { useDispatch } from 'react-redux';
...
const dispatch = useDispatch();
...
function resolveBoard(row, index) {
...
dispatch(updateSquareValue(squareProp)).then(() => {
resolveBoard(nextRow, nextIndex);
});
}
redux-thunk:
export const updateSquareValue = (key, value) => dispatch => {
dispatch({
type: 'UPDATE_SQUARE_VALUE',
key,
value,
});
return Promise.resolve();
};
Then in your component:
import { useDispatch } from 'react-redux';
...
const dispatch = useDispatch();
...
function resolveBoard(row, index) {
...
dispatch(updateSquareValue(squareProp)).then(() => {
resolveBoard(nextRow, nextIndex);
});
}
https://github.com/reduxjs/reselect/blob/master/src/index.js#L89
export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
let lastArgs = null
let lastResult = null
// we reference arguments instead of spreading them for performance reasons
return function () {
if (!areArgumentsShallowlyEqual(equalityCheck, lastArgs, arguments)) {
// apply arguments instead of spreading for performance.
lastResult = func.apply(null, arguments)
}
lastArgs = arguments
return lastResult
}
}
export function createSelectorCreator(memoize, ...memoizeOptions) {
return (...funcs) => {
let recomputations = 0
const resultFunc = funcs.pop()
const dependencies = getDependencies(funcs)
const memoizedResultFunc = memoize(
function () {
recomputations++
// apply arguments instead of spreading for performance.
return resultFunc.apply(null, arguments)
},
...memoizeOptions
)
...}
}
export const createSelector = createSelectorCreator(defaultMemoize)
So if I create createSelector(getUsers, (users) => users) for making a simple example. How is it run behind with the codes from above ?
createSelectorCreator(defaultMemoize) is called with getUsers, (users) => users inputs. Now defaultMemoize is also a function that returns a function. How are they all interacting to return the value ?
I think more important to how reselect works is why one should use it. The main reasons are composability and memomization:
Composability
Another way of saying this is that you write a selector once and re use it in other more detailed selectors. Lets say I have a state like this: {data:{people:[person,person ...]} Then I can write a filterPerson like this:
const selectData = state => state.data;
const selectDataEntity = createSelector(
selectData,//re use selectData
(_, entity) => entity,
(data, entity) => data[entity]
);
const filterDataEntity = createSelector(
selectDataEntity,//re use selectDataEntity
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
If I move data to state.apiResult then I only need to change selectData, this maximizes re use of code and minimizes duplication of implementation.
Memoization
Memoization means that when you call a function with the same arguments multiple times the function will only be executed once. Pure functions return the same result given the same arguments no matter how many times they are called or when they are called.
This means that you don't need to execute the function when you call it again with the same parameters because you already know the result.
Memoization can be used to not call expensive functions (like filtering a large array). In React memoization is important because pure components will re render if props change:
const mapStateToProps = state => {
//already assuming where data is and people is not
// a constant
return {
USPeople: state.data.people.filter(person=>person.address.countey === US)
}
}
Even if state.data.people didn't change the filter function would return a new array every time.
How it works
Below is a re write of createSelector with some comments. Removed some code that would safety check parameters and allow you to call createSelector with an array of functions. Please comment if there is anything you have difficulty understanding.
const memoize = fn => {
let lastResult,
//initial last arguments is not going to be the same
// as anything you will pass to the function the first time
lastArguments = [{}];
return (...currentArgs) => {//returning memoized function
//check if currently passed arguments are the same as
// arguments passed last time
const sameArgs =
currentArgs.length === lastArguments.length &&
lastArguments.reduce(
(result, lastArg, index) =>
result && Object.is(lastArg, currentArgs[index]),
true
);
if (sameArgs) {
//current arguments are same as last so just
// return the last result and don't execute function
return lastResult;
}
//current arguments are not the same as last time
// or function called for the first time, execute the
// function and set last result
lastResult = fn.apply(null, currentArgs);
//set last args to current args
lastArguments = currentArgs;
//return result
return lastResult;
};
};
const createSelector = (...functions) => {
//get the last function by popping it off of functions
// this mutates functions so functions does not have the
// last function on it anymore
// also memoize the last function
const lastFunction = memoize(functions.pop());
//return a selector function
return (...args) => {
//execute all the functions (last was already removed)
const argsForLastFunction = functions.map(fn =>
fn.apply(null, args)
);
//return the result of a call to lastFunction with the
// result of the other functions as arguments
return lastFunction.apply(null, argsForLastFunction);
};
};
//selector to get data from state
const selectData = state => state.data;
//select a particular entity from state data
// has 2 arguments: state and entity where entity
// is a string (like 'people')
const selectDataEntity = createSelector(
selectData,
(_, entity) => entity,
(data, entity) => data[entity]
);
//select an entity from state data and filter it
// has 3 arguments: state, entity and filterFunction
// entity is string (like 'people') filter is a function like:
// person=>person.address.country === US
const filterDataEntity = createSelector(
selectDataEntity,
(a, b, filter) => filter,
(entities, filter) => entities.filter(filter)
);
//some constants
const US = 'US';
const PEOPLE = 'people';
//the state (like state from redux in connect or useSelector)
const state = {
data: {
people: [
{ address: { country: 'US' } },
{ address: { country: 'CA' } },
],
},
};
//the filter function to get people from the US
const filterPeopleUS = person =>
person.address.country === US;
//get people from the US first time
const peopleInUS1 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
//get people from the US second time
const peopleInUS2 = filterDataEntity(
state,
PEOPLE,
filterPeopleUS
);
console.log('people in the US:', peopleInUS1);
console.log(
'first and second time is same:',
peopleInUS1 === peopleInUS2
);