How to call another generator in redux-saga? - redux

I would like to fetch detail information with getTemplateCoins generator from each item's id of fetched list in the same generator. My idea is to iterate through active and unactive list and return new list with additional information
MyCollectionSaga
export function* getMyCollectionDetails(api) {
try {
const responseDetails = yield makeRequest(api, api.getMySocialProfile, null);
const responseBadges = yield makeRequest(api, api.getBadges, null);
const responseTemplates = yield makeRequest(api, api.getTemplates, null);
if (responseDetails.ok && responseBadges.ok && responseTemplates.ok) {
const templates = responseTemplates.data.results.map((e) => parseTemplate(e));
const active = templates.filter((e) => e.active);
const unactive = templates.filter((e) => !e.active);
//Would like to call getTemplateCoins here with templateId as item's id of active and unactive list above.
//then save them in the new array and pass them to getMyCollectionDetailSuccess below
yield put(
MyCollectionsActions.getMyCollectionDetailsSuccess(
responseDetails.data.result,
responseBadges.data.result,
active,
unactive
)
);
}
} catch (error) {`
log(error);
}
}
export function* getTemplateCoins(api, { templateId }) {
try {
const params = { templateId };
const response = yield makeRequest(api, api.getTemplateCoins, params);
if (response.ok) {
const coinsCollections = response.data.results.coins.filter((e) => e.inCollection);
const coinsMissing = response.data.results.coins.filter((e) => !e.inCollection);
const coins = [
{ title: i18n.t('common:collectionTemplateSectionCollected'), data: coinsCollections },
{ title: i18n.t('common:collectionTemplateSectionMissing'), data: coinsMissing },
];
yield put(CollectionTemplateActions.getTemplateCoinsSuccess(coins));
} else {
yield put(
CollectionTemplateActions.getTemplateCoinsFailure(i18n.t('common:genericErrorMessage'))
);
}
} catch (error) {
log(error);
yield put(
CollectionTemplateActions.getTemplateCoinsFailure(i18n.t('common:genericErrorMessage'))
);
}
}`
Here is the redux function
MyCollectionRedux
const getMyCollectionDetails = (state) => {
return { ...state, isLoading: true, error: false, message: '' };
};
const getMyCollectionDetailsSuccess = (
state,
{ socialProfile, allBadges, templatesActivated, templatesUnactivated }
) => {
return {
...state,
socialProfile,
allBadges,
templatesActivated,
templatesUnactivated,
isLoading: false,
error: false,
};
};
const getMyCollectionDetailsFailure = (state, { message }) => {
return { ...state, message, isLoading: false, error: true };
};

Related

How to mutation store state in build query redux toolkit

Created an initialState and will be updated the totalPage and currentPage after got the users list.
I found out onQueryStarted from docs, it able to update the store state in this method but only look like only for builder.mutation.
what's the correct way to get the user list and update the store page value in redux toolkit?
Listing two part of the code below:
apiSlice
component to use the hook
// 1. apiSlice
const usersAdapter = createEntityAdapter({})
export const initialState = usersAdapter.getInitialState({
totalPage: 0,
currentPage: 0,
})
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: (builder) => ({
getUsers: builder.query({ // <--- the docs are using builder.mutation, but i needed to pass params
query: (args) => {
const { page, limit } = args;
return {
url: `/api/users`,
method: "GET",
params: { page, limit },
}
},
validateStatus: (response, result) => {
return response.status === 200 && !result.isError
},
transformResponse: (responseData) => { // <<-- return { totalPages: 10, currentPage: 1, users: [{}] }
const loadedUsers = responseData?.users.map((user) => user)
return usersAdapter.setAll(initialState, loadedUsers)
},
async onQueryStarted(arg, { dispatch, queryFulfilled }) {
try {
const { data } = await queryFulfilled
const {totalPages, currentPage} = data; <----- totalPages & currentPage values are still 0 as initialState
dispatch(setPages({ currentPage, totalPages }))
} catch (error) {
console.error("User Error: ", error)
}
},
providesTags: (result, error, arg) => {
if (result?.ids) {
return [
{ type: "User", id: "LIST" },
...result.ids.map((id) => ({ type: "User", id })),
]
} else return [{ type: "User", id: "LIST" }]
},
})
})
});
export const {
useGetUsersQuery,
} = usersApiSlice
component to use the hook
Try to use the hook in user landing page
const UsersList = () => {
const { data: users, isLoading, isSuccess, isError } = useGetUsersQuery({page: 1, limit: 10 })
return (
<div>return the users data</div>
)
}
update the store value after get the data return

How to call RTK mutations after first finishes

I am using REDUX-TOOLKIT-QUERY, Now I have a situation, I have to call one mutation. once that mutation returns a response, and I have to use that response in the other three mutations as a request parameter. How can we implement this in a proper way?
const [getSettings, { isLoading, data: Settings }] =useSettingsMutation();
const [getMenu] =useMenuMutation();
const [getServices] = useServicesMutation();
useEffect(() => {
const obj = {
Name: "someNme",
};
if (obj) getSettings(obj);
if (Settings?._id ) {
const id = settings._id;
getServices(id);
getMenu(id);
getServices(id);
}
}, []);
useMutation will return a promise, you can directly await this promise to get the return result:
const [getSettings, { isLoading, data: Settings }] = useSettingsMutation()
const [getMenu] = useMenuMutation()
const [getServices] = useServicesMutation()
useEffect(() => {
;(async () => {
try {
const obj = {
Name: 'someNme',
}
const data = await getSettings(obj).unwrap()
if (data._id !== undefined) {
const id = data._id
getServices(id)
getMenu(id)
getServices(id)
// ...
}
} catch (err) {
// ...
}
})()
}, [])

Why filter method in my reducer returns an array of proxy? -Redux Toolkit

so i want to delete an item from array, onClick but when i log the filtered data in the console i get an array of Proxy.
i tried Changing my code but nothing worked
whats wrong here in itemRemoved?
import { createSlice, createAction } from "#reduxjs/toolkit";
// Action Creater
const slice = createSlice({
name: "shoppingCart",
initialState: [],
reducers: {
itemAdded: some code // ,
itemRemoved: (cart, { payload }) => {
cart.filter((item) => {
if (item.id === payload.id) {
if (item.count === 1) {
return cart.filter((item) => item.id !== payload.id);
}
else {
const itemIndex = cart.indexOf(item);
cart[itemIndex].count = cart[itemIndex].count - 1;
return cart;
}
}
});
},
},
});
export const { itemAdded, itemRemoved } = slice.actions;
export default slice.reducer;
Assuming you want to remove the element with the id you are passing through the dispatch function
itemRemoved: (state, { payload }) => {
const newCart = state.cart.filter(item => item.id !== payload.id)
const state.cart = newCart
return state
}),

TypeError: defaultPieces inside of componentDidMount is not a function

Inside of my PossibleMatches component, I have imported a few asynchronous functions from my actions/index file and as I call these functions inside of componentDidMount, the Error I'm getting back is: defaultPieces is not a function.
Below are the contents of my PossibleMatches component and actions/index.js file:
For the sake of brevity, I did my best to add everything that is relevant to the main problem.
PossibleMatches.js
import { connect } from 'react-redux';
import {arrangePieces, defaultPieces, setEvaluatedPiece, getCorrespondingPieces} from '../actions/index';
constructor(props){
super(props);
const initialState = {
currentComponent: {whichPiece: {whichType: null, currentUpperComponent: null, currentLowerComponent: null}},
UpperComponents: this.props.UpperComponents,
LowerComponents: this.props.LowerComponents,
UpperComponentEnabled: false,
LowerComponentEnabled: false,
isFetched: false,
isFetching: true
}
this.state = {
...initialState
}
this.residingUpperComponent = createRef();
this.residingLowerComponent = createRef();
//Also need to this.prop.setEvaluatedPiece based off of this.props.setCurrentComponent if callback from Lower or Upper Components elapses time
this.setNewPiece = this.setNewPiece.bind(this);
this.renderDecision = this.renderDecision.bind(this);
}
async componentDidMount(){
const {store} = this.context;
let stateRef = store.getState();
const { defaultPieces, arrangePieces } = this.props;
try {
const summon = () => { defaultPieces();
arrangePieces();}
await summon();
} catch(error){
throw Error(error);
}
console.log("AFEWOVMAOVHIE")
this.setState({isFetched: true, isFetching: false});
}
renderDecision(){
const { currentComponent, LowerComponentEnabled, UpperComponentEnabled, isFetching, isFetched} = this.state;
const { suggestedBottoms, suggestedTops, UpperComponents, LowerComponents } = this.props;
if (isFetching){
return (<div className='activityLoader'>
<ActivityIndicator number={3} duration={200} activeColor="#fff" borderWidth={2} borderColor="50%" diameter={20}/>
</div>);
} else if (isFetched){
return (
<div className = "PossibleMatches_Container">
<i className = 'captureOutfit' onClick = {this.snapshotMatch}></i>
<TransitionGroup component = {PossibleMatches}>
** ** ** {UpperComponents.map((component) => {
return (<UpperComponent key={component.created_at} id={component.id}
switchComponent={this.switchFocus}
setCurrentPiece={this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {() => {if (LowerComponentEnabled === false){this.setState({LowerComponentEnabled: true})} else return; this.setState({currentLowerComponent: suggestedBottoms[0]})}}
isLowerComponentEnabled = {LowerComponentEnabled}
ref = {this.residingUpperComponent}
className = {currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>
)
})
}
</TransitionGroup>
<TransitionGroup component = {PossibleMatches}>
{LowerComponents.map((component) => {
return (<LowerComponent key={component.created_at} id={component.id}
setCurrentPiece = {this.setNewPiece}
evaluatePiece={this.isOppositeComponentSuggested}
image={component.image}
toggleToPiece = {() => {if (UpperComponentEnabled === false){this.setState({UpperComponentEnabled: true})} else return; this.setState({currentUpperComponent: suggestedTops[0]})}}
switchComponent = {this.switchFocus}
isUpperComponentEnabled = {UpperComponentEnabled}
ref = {this.residingLowerComponent}
className = {this.state.currentComponent.whichPiece.whichType === 'match' ? 'PossibleMatches_Container' : this.state.currentComponent.whichPiece.whichType === 'bottom' ? 'standalonePiece' : 'standalonePiece'}/>)
})
}
</TransitionGroup>
</div>
)
}
}
render(){
return(
<div className = 'GorClothingContainer'>
<Wardrobe upperComponent={this.state.currentComponent.whichPiece.currentUpperComponent} lowerComponent={this.state.currentComponent.whichPiece.currentLowerComponent} enableCapture={(snapshot) => this.snapshotMatch = snapshot} />
{this.renderDecision()}
</div>
);
}
function mapStateToProps(state){
const { UpperComponents, LowerComponents, contemplated_piece, extraTops, extraBottoms, standaloneBottoms, standaloneTops, suggestedBottoms, suggestedTops } = state.possibleMatches;
return {UpperComponents, LowerComponents, contemplated_piece, extraTops, extraBottoms, standaloneBottoms, standaloneTops, suggestedBottoms, suggestedTops };
}
export default connect(mapStateToProps, {defaultPieces, arrangePieces, getCorrespondingPieces, setEvaluatedPiece})(PossibleMatches)
Inside of my actions/index.js
export function defaultPieces(){
return function(dispatch){
fetch(`${API_URL}/possible_matches/setup_possible_matches`, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}).then((res) => res.json())
.then((json) => {
console.log('defaultPieces: ', json);
dispatch(getInitialPieces(json))
})
}
}
export function getInitialPieces(request){
return {
type: INITIAL_PIECES,
payload: request
}
}
Inside of PossibleMatches reducer:
import {INITIAL_PIECES, GET_ANCILLARY_PIECES, ORGANIZE_PIECES, SET_CONTEMPLATED_PIECE} from '../actions/types';
const initialState = {
UpperComponents: [],
LowerComponents: [],
contemplated_piece: null,
extraTops: [],
extraBottoms: [],
standaloneTops: [],
standaloneBottoms: [],
suggestedTops: [],
suggestedBottoms: []
}
export default function(state = initialState, action){
switch(action.type){
case INITIAL_PIECES:
return {...state, {contemplated_piece: action.payload.contemplated_piece,
extraTops: action.payload.extra_tops,
extraBottoms: action.payload.extra_bottoms,
standaloneTops: action.payload.standalone_tops,
standaloneBottoms: action.payload.standalone_bottoms,
suggestedTops: action.payload.suggested_tops,
suggestedBottoms: action.payload.suggested_bottoms}
case GET_ANCILLARY_PIECES:
return {...state, extraTops: action.payload.extra_tops,
extraBottoms: action.payload.extra_bottoms,
standaloneTops: action.payload.standalone_tops,
standaloneBottoms: action.payload.standalone_bottoms,
suggestedTops: action.payload.suggested_tops,
suggestedBottoms: action.payload.suggested_bottoms}
case ORGANIZE_PIECES:
return {...state, UpperComponents: action.payload.UpperComponents,
LowerComponents: action.payload.LowerComponents}
case SET_CONTEMPLATED_PIECE:
return {...state, contemplated_piece: action.payload.contemplated_piece}
default:
return state;
}
}
Because defaultPieces is not a valid function to the PossibleMatches components, it interferes with the interpretation of the UpperComponents prop that comes from mapStateToProps function (denoted with an * above).
What is peculiar is the json logged out to the console from both the arrangePieces and defaultPieces methods:
It was a bizarre fix, but I basically needed to set conditions in ComponentDidMount to halt the program based on the state of UpperComponents relative to what got returned from this.props.arrangePieces().
if (UpperComponents.length === 0){
return;
} else {
this.setState({isFetched: true, isFetching: false});
}
Since the component rerenders, I thought it was idealistic to add a componentWillRecieveProps lifecycle method to deal with incoming props:
componentWillReceiveProps(nextProps){
if (nextProps.contemplated_piece !== this.props.contemplated_piece){
if (nextProps.contemplated_piece.merch_type === 'top'){
this.setState({currentLowerComponent: nextProps.suggestedBottoms[0],
currentUpperComponent: nextProps.contemplated_piece});
}
else if (nextProps.contemplated_piece.merch_type === 'bottom'){
this.setState({currentLowerComponent: nextProps.contemplated_piece,
currentUpperComponent: nextProps.suggestedTops[0]});
}
}
else {
return null;
}
}

Correct reducers composition

How to get reducers not to return always new state? Is my solution ok or there are some better way?
My state shape:
userList: {
id: 'xxx', // <-|- Not mutable by reducers fields, so using combineReducers for each field is frustrating.
ba: 'xxx', // <-|
fo: 'xxx', // <-|
users: [
{
id: 'yyy',
be: 'yyy',
fo: 'yyy',
photo: {
id: 'zzz',
url: 'http://image.jpg', <-- The only field, that changes by reducers.
},
},
...
],
}
My current reducers:
// Always returns new object.
const userListReducer = (userList, action) => {
return {
...userList,
users: usersReducer(userList, action),
}
};
// Always returns new array.
const usersReducer = (users, action) => {
return users.map(user => userReducer(user, action));
};
// Always returns new object too.
const userReducer = (user, action) => {
return {
...user,
photo: photoReducer(user.photo, action),
};
};
// The only one true reducer.
const photoReducer = (photo, action) => {
switch (action.type) {
case 'update_photo':
return {
...photo,
url: action.url,
};
default:
return photo;
}
};
Solutions
1). Call usersReducer when it's necessary. Bad part: we need to care about logic other reducers.
const userListReducer = (userList, action) => {
switch (action.type) {
case 'update_photo':
return {
...userList,
users: usersReducer(userList, action),
};
default:
return userList;
}
};
2). Using combineReducers. Bad part: we need to care about all usersList's shape. Also, this still doesn't work, because usersReducer always returns new array.
const userListRecuer = combineReducers({
id: id => id,
ba: ba => ba,
fo: fo => fo,
users: usersReducer,
});
3). My solution. Using mergeOrReturnOld and mapOrReturnOld helpers.
const userListReducer = (userList, action) => {
return mergeOrReturnOld(userList, {
users: usersReducer(userList, action),
});
};
const usersReducer = (users, action) => {
return mapOrReturnOld(users, user => userReducer(user, action));
};
const userReducer = (user, action) => {
return mergeOrReturnOld(user, {
photo: photoReducer(user.photo, action),
});
};
helpers implementation:
const mergeOrReturnOld = (obj, addition) => {
let hasChanged = false;
for (let key in addition) {
if (addition.hasOwnProperty(key)) {
if (obj[key] !== addition[key]) {
hasChanged = true;
}
}
}
if (!hasChanged) {
return obj;
} else {
return {
...obj,
...addition,
};
}
};
const mapOrReturnOld = (array, callback) => {
let hasChanged = false;
const newArray = array.map(item => {
const newItem = callback(item);
if (newItem !== item) {
hasChanged = true;
}
return newItem;
});
if (!hasChanged) {
return array;
} else {
return newArray;
}
};

Resources