react, reselect and redux asyncThunk

37 Views Asked by At

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);

2

There are 2 best solutions below

0
Konstantin On BEST ANSWER

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;
0
Linda Paiste On

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) => {