Nested object destructuring with redux and react - redux

I'm following a tutorial in React Redux. I have created a store variable with Redux store which has two sub variables. One is expenses which is an array of objects and another is filters which is an object itself.
const store = createStore(
combineReducers({
expenses: expensesReducer,
filters: filtersReducer
})
);
When filled with dummy values the store would look like this:
const dummyState = {
expenses: [{
id: '10-AC-191',
title: 'January Rent',
note: 'This was the final payment for that address',
amount: 545.00,
createdAt: 0
}, {
id: '10-AK-155',
title: 'Breakfast',
amount: 545.00,
createdAt: 2000
}],
filters: {
text: 'rent',
sortBy: 'amount',
startDate: 700,
endDate: 360,
}
};
I'm currently writing a function to display resultant expenses array which looks like this.
const getVisibleExpenses = (expenses, {text, sortBy, startDate, endDate}) => {
return expenses.filter(({title, note, createdAt}) => {
const startDateMatch = typeof startDate !== 'number' || createdAt >= startDate;
const endDateMatch = typeof endDate !== 'number' || createdAt <= endDate;
const searchText = text.trim().toLowerCase();
const textMatch = typeof text !== 'string' || title.toLowerCase().includes(searchText)
|| note.toLowerCase().includes(searchText);
return startDateMatch && endDateMatch && textMatch;
}).sort((expense_a, expense_b) => {
if (sortBy === 'amount') return expense_a.amount - expense_b.amount;
else if (sortBy === 'date') return expense_a.createdAt - expense_b.createdAt;
});
};
This function takes store.expenses, store.filters as two inputs. So I wanted to pass in store object only and get the output.
Hence I tried to object destructure the input store itself instead of calling store below. But it returns an error.
const getVisibleExpenses = ({expenses, {text, sortBy, startDate, endDate}})
Is there any possible solutions?

Try this:
const getVisibleExpenses = ({expenses, filters: {text, sortBy, startDate, endDate}}) => {}

Related

react, reselect and redux asyncThunk

how can i use custom groupby function for data that i get from asyncThunk
I want to store in "redux store" original array that i obtain from api
and then i want to change data(by using groupby function) and display it
for example, I have a function thant call api
`
export const getAnimals = createAsyncThunk(
'animals/getAnimals',
async function(_, {rejectWithValue}) {
try {
const response = await fetch('http://127.0.0.1:3001/animals')
if (!response.ok) {
throw new Error('Problem');
}
const data = await response.json();
return data;
} catch (error) {
return rejectWithValue(error.message)
}
}
);
and such array from api
"animals": [
{"animal_type": "dog","name": "Jack", "id":1},
{"animal_type": "cat","name": "Kitty", "id":2},
{"animal_type": "bird","name": "Red", "id":3},
{"animal_type": "dog","name": "Tatoshka", "id":4},
{"animal_type": "dog","name": "Rasl", "id":5},
{"animal_type": "bird","name": "blue", "id":6},
{"animal_type": "cat","name": "murr", "id":7},
{"animal_type": "snake","name": "Van", "id":8},
{"animal_type": "cat","name": "kshh", "id":9},
{"animal_type": "dog","name": "Mailo", "id":10},
{"animal_type": "cat","name": "barsik", "id":11},
{"animal_type": "monkey","name": "Ika", "id":12}
]
I have a slice with extraReducer
const animalSlice = createSlice({
name: 'animals',
initialState: {
animals: [],
loading: null,
error: null,
},
extraReducers: {
[getAnimals.pending]: (state) => {
state.loading = true;
state.error = null;
},
[ggetAnimals.fulfilled]: (state, action) => {
state.loading = false;
state.animals = action.payload;
},
[getAnimals.rejected]: setError,
}
})
`
in a companent i do something like that
`
const fitOptions = [];
{
Object.keys(animals).forEach(function(animal_type, index){
fitOptions.push(
<Menu.Item key={index}>
<Accordion.Title
active={activeIndex === index}
content={animal_type}
index={index}
onClick={() => accordionClick(index)}
/>
<Accordion.Content active={activeIndex === index} content={
<Form>
<Form.Group grouped>
{animals[animal_type].map((animal) =>
<Form.Checkbox label={animal.name} name='animal_id' value={animal.id} key={animal.id} />
)}
</Form.Group>
</Form>
} />
</Menu.Item>
);
})
}
`
and I have a function groupBu that I earlyer call in reducer and as a result save in store "changed" data, but now I want to store in redux store original array and do a groupBy in reselect
`
const groupBy = (xs, key) => {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
`
But I have an error, because this function starts before i get a result of api call.
It's seems it must by Promise object as a result of a call, but I can't find a way to use it in this proupBy function
I will be appreciative for your help
I have tryed to create reselect
`
export const selectAnimaksByFilter = createSelector(
[selectAllAnimals, selectActiveFilter],
(allAnimals, activeFilter) => {
if (!activeFilter) {
const grouped = groupBy(allAnimals, 'animal_type');
return grouped;
}
}
);
`
and get in a component as
const animals = useSelector(selectAnimaksByFilter);
I moves groupBy to component before a render
and set with .slice() method
i.e.
const animals = useSelector(selectAllAnimals);
const grouped = groupBy(animals.slice(), 'animal_type');
selectAllAnimals defined in a store
export const selectAllAnimals = (state) => state.animals.animals;
It's fine to use reselect's createSelector here. You want to make sure that your function won't crash if the the data hasn't been loaded yet.
In your case it won't be an issue because your initial value of state.animals.animals is an empty array [] (not undefined), but I'm going to use a fallback empty array value anyways just to be safe.
export const selectAllAnimals = (state) => state.animals.animals;
export const selectAnimalsByType = createSelector(
selectAllAnimals,
(allAnimals = []) => groupBy(allAnimals, 'animal_type')
);
In the component:
const animalsByType = useSelector(selectAnimalsByType);
Here is a version which gets the grouping field from another selector, and defaults to grouping by 'animal_type' if not set:
export const selectAnimalsByFilter = createSelector(
[selectAllAnimals, selectActiveFilter],
(allAnimals = [], activeFilter = 'animal_type') => groupBy(allAnimals, activeFilter)
);
It looks like there is a typo in your reducer (gget instead of get):
[ggetAnimals.fulfilled]: (state, action) => {

Selector returns empty array from the redux state, even though the array has values

I have the following normalized redux state:
rootReducer: {
blocks: {
"key1": {
id: "key1",
beverages: [], // Array of objects
}
}
}
and I'm trying to select the value of beverages for beverage with the id of "key1" using this selector:
export const getBlockBeverages = (state, blockId) => {
console.log("selector", state.blocks[blockId].beverages);
return state.blocks[blockId].beverages;
};
Whenever I add a new beverage into the beverages array, the selector gets called twice, first time with an empty array, second time with proper values:
Initial state
selector []
selector []
Adding new beverage:
selector []
selector [{/*beverage1*/}]
// Adding another beverage
selector []
selector [{/*beverage1*/}, {/*beverage2*/}]
I'd really appreciate any help/explanation why does the selector get called and beverages value for the block instance is an empty array.
Below is the code for reducers I'm using - I don't see where I could be mutating the original state, I used Immer's produce from the beginning and the problem is still present. Then I tried to use lodash.clonedeep to make sure that I return a new state, but the selector still logs that empty array.
const blockReducer = (state = { id: "", beverages: [] }, action) => {
if (action.type === ADD_BEVERAGE_TO_BLOCK) {
const { beverageId } = action.payload;
const newBeverage = { id: uuid4(), beverageId };
return produce(state, (draft) => {
draft.beverages.push(newBeverage);
});
}
return state;
};
const blocks = (state = {}, action) => {
const key = action.payload.key;
if (key && (state[key] || action.type === CREATE_BLOCK)) {
const instanceState = blockReducer(state[key], action);
return produce(state, (draft: any) => {
draft[key] = instanceState;
});
}
return state;
};
Any ideas why the selector returns empty array instead of array of length 0, 1, 2 etc. as I'm adding new beverages? I'm clueless and will appreciate any help.
The problem was in a different selector that I had been using in a wrong way.
export const makeGetBlockBeveragesLength = () => createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
and instead of mapStateToProps I used createMapStateToProps:
const createMapStateToProps = (state, { blockId }) => () => {
const getBlockBeveragesLength = makeGetBlockBeveragesLength();
return {
length: getBlockBeveragesLength(state, blockId),
};
};
export const Component = connect(createMapStateToProps)(MyComponent);
The empty array logged in one of the logs refers to an older state (the initial state in this case).
I fixed the code to this and it works:
export const getBlockBeveragesLength = createSelector(
(state, blockId) => getBlockBeverages(state, blockId),
(blockBeverages) => blockBeverages.length,
);
const mapStateToProps = (state, { blockId }) => ({
length: getBlockBeveragesLength(state, blockId),
});
export const Component = connect(mapStateToProps)(MyComponent);

Redux toolkit - Cannot use 'in' operator error on upsertMany

I am following the docs to upsertMany into my redux createSlice.
I am following this part of the docs.
I keep getting this error in my upsertMany call. Why is this?
Unhandled Rejection (TypeError): Cannot use 'in' operator to search for '525' in undefined
I have noticed that normalizr returns both entities and result objects, however, RTK only uses entities. Where do we use the result if we need to at all?
Here is my createSlice
const posts = new schema.Entity('actionPosts', {}, { idAttribute: 'id' })
export const fetchPosts = createAsyncThunk(
'actionPosts/fetchPosts',
async (email) => {
const { data } = await getUserActionPostsByUser({ email })
const extractedPosts = data.userActionPostsByUser
const normalizedData = normalize(extractedPosts, [posts])
return normalizedData.entities
}
)
const adapter = createEntityAdapter({
sortComparer: (a, b) => b.createdAt.localeCompare(a.createdAt),
loading: '',
error: '',
})
const initialState = adapter.getInitialState()
const slice = createSlice({
name: 'actionPosts',
initialState,
extraReducers: {
[fetchPosts.fulfilled]: (state, { payload }) => {
console.log('payload', payload.actionPosts)
adapter.upsertMany(state, payload.actionPosts) // error happens here
},
},
})
export default slice.reducer
Here is the normalized object
{
actionPosts: {
525: {
id: 525
email: "test#test.com"
content: "lorem ipsum"
createdAt: "2020-09-24T20:29:44.848Z"
}
}
result[
525,
]
}

How to get a entity by ID ngrx

I have the following store
export const featureAdapter: EntityAdapter<IProduct> = createEntityAdapter<IProduct>({
selectId: model => model.id,
});
export interface State extends EntityState<IProduct> {
selectedProduct?: IProduct;
isLoading?: boolean;
error?: any;
}
export const initialState: State = featureAdapter.getInitialState({
selectedProduct: IProduct,
isLoading: false,
error: null
});
I would like that my selected product always point on a Entity and get updates with it. I believe since actions are always creating new object, the link is not possible, I decided to change the selectedProduct from a reference to a simple id.
export const initialState: State = featureAdapter.getInitialState({
selectedProduct: string,
isLoading: false,
error: null
});
but how do i retrieve my entity with the same ID, and get updates on my observable if it is changed ?
I tried
export const getSelectedProduct: MemoizedSelector<object, any> = createSelector(
selectAllEntities,
selectedProduct,
(entities, id) => entities[id]
);
export const selectEntityById = createSelector(
selectAllEntities,
(entities, props) => entities[props.id]
);
my questions are
Is this the only way to select an entity by id ?
this.store$.pipe(
select(ProductStoreSelectors.selectEntityById, { id: product.id }),
map(product => console.log(product))
)
and
this.store$.select(ProductStoreSelectors.getSelectedProduct).subscribe((product: string) => {
console.log(product)
}),
this never trigger when I change my selected product Id
EDIT :
the reducer on select do the following
const SET_SELECTED_PRODUCT = (state: State, action: featureAction.SetSelectedProduct) => {
return {
...state,
selectedProduct: action.payload.product.id
};
};
Try something like this in your selector:
export const selectEntityById = (id: number) => createSelector(
selectAllEntities,
(entities) => entities[id]
);
and your select:
select(ProductStoreSelectors.selectEntityById(id))

How would you write the condition in ramda?

I'm new to Ramda and just trying to wrap my head around it. So here is the function I want to rewrite in functional style:
const makeReducer = (state, action) => {
if (action.type === LOG_OUT) {
return rootReducer(undefined, action)
}
return rootReducer(state, action)
}
Here is what I end up with:
const isAction = type => R.compose(R.equals(type), R.prop('type'))
const makeReducer = (state, action) => {
const isLogOut = isAction(LOG_OUT)
return R.ifElse(isLogOut, rootReducer(undefined, action), rootReducer(state, action))(action)
}
I assume it's totally wrong as there are several duplications of action and rootReducer
Actually I don't see any reason to refactor your code: you're not mutating inputs and you use if to conditionally return outputs.
About rootReducer(undefined, action), I believe that you should use parameter destructuring:
const rootReducer = ({ state, action } = {}} => {
// Stuff here
}
That is, you may give either state or action, or both:
const makeReducer = ({ state, action }) => {
if (action.type === LOG_OUT) {
return rootReducer({ action })
}
return rootReducer({ state, action })
}
Also, consider using terniary to solve simple cases:
const makeReducer = ({ state, action }) =>
rootReducer( action.type === LOG_OUT ? { action } : { state, action } )
Finally, there could be yet another approach using tagged sums and folds. Since I don't work with React and/or Redux, I don't know if you could go with this approach but I believe that it's still interesting that you discover this alternative solution:
const tag = Symbol ( 'tag' )
// TaggedSum
const Action = {
logout: value => ( { [tag]: 'logout', value } ),
login: value => ( { [tag]: 'login', value } )
}
const foldAction = matches => action => {
const actionTag = action[ tag ]
const match = matches [ actionTag ]
return match ( action.value )
}
const state = { x: 1 }
const LOG_IN = 1
const LOG_OUT = 2
const logout = Action.logout ( { action: LOG_OUT, state } )
const login = Action.login ( { action: LOG_IN, state } )
const rootReducer = args => console.log ( args )
// Pattern matching
const matchAction = {
logout: ( { state } ) => rootReducer( { state } ),
login: rootReducer
}
const foldAction_ = foldAction( matchAction )
foldAction_ ( logout )
foldAction_ ( login )
You can get rid of the duplication fairly easily:
const makeReducer = (state, action) =>
rootReducer((action.type === LOG_OUT ? undefined : state), action)
That is really neither more nor less functional than the original. But it does have the advantage of reducing duplication, and of dealing only with expressions and not statements, which is sometimes a concern of functional techniques.
But there is one way in which it is clearly not functional. There is a free variable in your code: LOG_OUT. I'm guessing from the ALL_CAPS that this is meant to be a constant. But the function doesn't know that. So this function is not actually referentially transparent. It's possible that between invocations with the same parameters, someone changes the value of LOG_OUT and you could get different results.
This makes the function harder to test. (You can't just supply it the necessary parameters; you also have to have the correct value of LOG_OUT in scope.) And it makes it much harder to reason about.
An alternative without this issue is
const makeReducer = (state, action, types) =>
rootReducer((action.type === types.LOG_OUT ? undefined : state), action)
If you want to use pointfree style syntax for your code, you could do something like:
const initialState = {
text: 'initial text'
}
const rootReducer = R.curry((state, action) => {
// setting initial state could be improved
state = state || initialState
// your root reducer logic here
return state;
})
// R.last is here to grab the action in [state, action]
const isAction = type => R.compose(R.equals(type), R.prop('type'), R.last)
// first makes (state, action) into [state, action]
// before running R.cond
const makeReducer = R.compose(R.cond([
[isAction('LOG_OUT'), R.compose(rootReducer(undefined), R.last)],
// "default" action
[R.T, R.apply(rootReducer)]
]), R.pair)
const loggedOutState = makeReducer(
{ text: 'latest text'},
{ type: 'LOG_OUT'}
)
console.log(loggedOutState)
// => { text: 'initial text' }
const nextState = makeReducer(
{ text: 'latest text'},
{ type: 'ANY_ACTION'}
)
console.log(nextState)
// => { text: 'latest text' }
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.25.0/ramda.js"></script>
What's good about this solution is that you could easily extend makeReducer to handle more actions (since it's using R.cond -- which is like a switch statement).

Resources