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) => {
Related
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);
I want to type using generics a function that simulates a query. To give the most flexibility to the type of data to be used (and to avoid returning same referenced data) this function takes another function to generate the data to return. Because this function can return any data type, I don't want to restrict it to any specific type, but I don't want to use any either.
I thought this could be something achievable with generics, but all my attempts to type it properly fail. Here is what I tried so far:
//#flow
import { useState, useEffect } from 'react'
export const makeUseQuery = <T>(generateData: () => T) => () => {
const [data, setData] = useState()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const id = setTimeout(() => {
setData(generateData())
setIsLoading(false)
}, 2500)
return () => {
clearTimeout(id)
}
}, [])
return {
data,
isLoading,
error: null,
}
}
The error I get from flow is that I can not let the generic escape from the scope, but I am not sure how else can I keep this type safe.
Maybe something like this?
import { useState, useEffect } from 'react'
type UseQueryResult<Response> = $ReadOnly<{|
data: Response | void,
isLoading: boolean,
error: null,
|}>;
export const makeUseQuery = <Response>(
generateData: () => Response
): (() => UseQueryResult<Response>) =>
(): UseQueryResult<Response> => {
const [data, setData] = useState()
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
const id = setTimeout(() => {
setData(generateData())
setIsLoading(false)
}, 2500)
return () => {
clearTimeout(id)
}
}, [])
return {
data,
isLoading,
error: null,
}
}
(try)
Running into an infinite loop when I try to dispatch an action which grabs all recent posts from state.
I have tried the following in useEffect dependency array
Object.values(statePosts)
useDeepCompare(statePosts)
passing dispatch
omitting dispatch
omitting statePosts
passing statePosts
doing the same thing in useCallback
a lot of the suggestions came from here
I have verified that data correctly updates in my redux store.
I have no idea why this is still happening
my component
const dispatch = useDispatch()
const { user } = useSelector((state) => state.user)
const { logs: statePosts } = useSelector((state) => state.actionPosts)
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
actionPosts createSlice
const slice = createSlice({
name: 'actionPosts',
initialState: {
posts: [],
},
reducers: {
postsLoading: (state, { payload }) => {
if (state.loading === 'idle') {
state.loading = 'pending'
}
},
postsReceived: (state, { payload }) => {
state.posts = payload
},
},
})
export default slice.reducer
const { postsReceived, postsLoading } = slice.actions
export const getActionPostsRest = (email) => async (dispatch) => {
try {
dispatch(postsLoading())
const { data } = await getUserActionPostsByUser({ email })
dispatch(postsReceived(data.userActionPostsByUser))
return data.userActionPostsByUser
} catch (error) {
throw new Error(error.message)
}
}
Remove dispatch from dependencies.
useEffect(() => {
dispatch(getActionLogsRest(user.email))
}, [user, dispatch, useDeepCompare(stateLogs)])
you cannot use hook as dependency and by the way, ref.current, is always undefined here
const useDeepCompare = (value) => {
const ref = useRef()
if (!_.isEqual(ref.current, value)) {
ref.current = value
}
return ref.current
}
because useDeepCompare essentially is just a function that you initiate (together with ref) on each call, all it does is just returns value. That's where the loop starts.
I am not able to retrieve the state in the reducer
MyComponent looks like this
const MyComponent = ({name, features, onClick}) => {
return (
<div>
Hello! {name}
<Button onClick={() => { onClick(features); }}> Weight</Button>
</div>
);
const mapDispatchToProps = (dispatch: any) => {
return {
onClick: (features) => {
dispatch(weightSort(features));
}
};
};
const mapStateToProps = (state: any, ownProps: any) => {
console.log(state); //Displays the state
return {
name: "John Doe",
features: ownProps.features,
};
};
export const FeatureBlock = connect(mapStateToProps, mapDispatchToProps)(MyComponent);
My actions and reducers looks like below:
// Action Creator
export const weightSort = (features) => {
console.log("inside the weight sort action creator!!!");
return {
type: "SET_WEIGHT_FILTER",
filter: "DESC",
features,
};
};
// Reducer
export const weightFilter = (state = [], action) => {
switch (action.type) {
case "SET_WEIGHT_FILTER":
console.log(state); // Gives me empty state
console.log("++inside weight filter+++++", action); //Displays action
return state;
default:
return state;
}
};
export const FeatureBlock = connect(
mapStateToProps,
mapDispatchToProps,
)(MyComponent);
What am I missing here? Any help will be appreciated!
In your reducer, when you console.log(state), it is correct in returning an empty array because you haven't done anything to modify it.
// Reducer
export const weightFilter = (state = [1,2,3], action) => {
switch (action.type) {
case "SET_WEIGHT_FILTER":
console.log(state); // This will show [1,2,3] because [1,2,3] is the initial state.
console.log("++inside weight filter+++++", action); //Displays action
return state;
default:
return state;
}
};
My guess is that you want something like this for your reducer:
// Action Creator
export const weightSort = (name, features) => {
console.log("inside the weight sort action creator!!!");
return {
type: "SET_WEIGHT_FILTER",
name,
features,
};
};
// Reducer
export const weightFilter = (
state = {
name: '',
features: [],
},
action
) => {
switch (action.type) {
case "SET_WEIGHT_FILTER":
return {...state, name: action.name, features: action.features}
default:
return state;
}
};
and then in your mapStateToProps you would map out the attributes like so:
const mapStateToProps = (state: any, ownProps: any) => {
console.log(state); //Displays the state
return {
name: state.weightFilter.name,
features: state.weightFilter.features,
};
};
and your button would have a name prop passed into the function like so:
<Button onClick={() => { onClick(name, features); }}> Weight</Button>
If you would like to sort your data, you can do so either in the reducer or inside the container. I prefer to do it in the container and like to use the lodash sortBy function. It works like this:
import { sortBy } from 'lodash' //be sure to npm install lodash if you use this utility
...
...
function mapStateToProps(state) {
return {
name: state.weightFilter.name,
features: sortBy(features, ['nameOfPropertyToSortBy'])
};
}
Here is the lodash documentation on sortBy: https://lodash.com/docs/4.17.4#sortBy
Hope that helps!
I am using reselect and react redux. I am trying to make a selector for a basic modal implementation.
my selector is
const selectModal = (state) => state.get('modal');
which throws the error of
Cannot read property 'get' of undefined
edit: It has been requested I show how I call select modal, though it should make no difference.
const mapStateToProps = createStructuredSelector({
isVisible: selectModalIsVisible(),
});
const mapDispatchToProps = {
hideModal,
showModal
};
export default connect(mapStateToProps, mapDispatchToProps)(Modal);
I believe this means the modal state container is not being found
Perhaps I am setting up my reducer or store incorrectly. My reducer is
function modalReducer(state = initialState, action) {
switch (action.type) {
case HIDE_MODAL:
return state.set(
'isVisible', false);
case SHOW_MODAL:
return state.set(
'isVisible', true);
default:
return state;
}
}
which is combined with combine reducers into a glob
export default function createReducer(asyncReducers){
return combineReducers({
route: routeReducer,
auth: authReducer,
modal: modalReducer,
...asyncReducers
});
}
and then injected into my store
function configureStore(initialState = {}, history) {
const middlewares = [
sagaMiddleware,
routerMiddleware(history),
];
const enhancers = [
applyMiddleware(...middlewares),
]
const store = createStore(
createReducer(),
fromJS(initialState),
compose(...enhancers)
);
store.runSaga = sagaMiddleware.run;
//store.close = () => store.dispatch(END)
store.runSaga(sagas);
store.asyncReducers = {};
return store;
}
var initialState = {}
const store = configureStore(fromJS(initialState), browserHistory);
The error within reselect is at lines 73/74 params = dependencies.map
var selector = function selector(state, props) {
for (var _len4 = arguments.length, args = Array(_len4 > 2 ? _len4 - 2 : 0), _key4 = 2; _key4 < _len4; _key4++) {
args[_key4 - 2] = arguments[_key4];
}
var params = dependencies.map(function (dependency) {
return dependency.apply(undefined, [state, props].concat(args));
});
return memoizedResultFunc.apply(undefined, _toConsumableArray(params));
};
So what am I doing wrong, do I need to do something with immutableJS differently to access the modal, or is my setup for the app incorrect? Thank you in advance for your feedback.
If you're using selectModal like you're using selectModalIsVisible, then your syntax is wrong. I'm pretty sure createStructuredSelector does not understand () => (state) => state.get('modal'). It would only accept (state) => state.get('modal')
Typically, my usages of createStructuredSelector will look like either
const getThing = (state, props) => state.things[props.thingId];
const getModal = state => state.get('modal');
const mapStateToProps = createStructuredSelector({
thing: getThing, // notice no parens
modal: getModal, // notice no parens
})
OR if I need selector factories:
// just pretend this selector was more complicated and needed memoization
const makeGetThing = () => createSelector(
state => state.things,
(state, props) => props.thingId,
(things, thingId) => things[thingId]);
const getModal = state => state.get('modal');
const makeMapStateToProps = () => createStructuredSelector({
thing: makeGetThing(), // yes parens
modal: getModal, // no parens
})