I am fetching journeys base on search criteria. I want to store those criteria in store.
I tied to access action.payload in extrareducer pending but it is not working. May I know how to store the criteria param into store.
import { createSlice, createAsyncThunk, PayloadAction } from '#reduxjs/toolkit'
import axios from "axios"
import { Journey, SearchCriteria } from '../types'
import constants from "../constants";
type InitialState = {
loading: boolean
journeys: Journey[]
criteria: SearchCriteria | null
error: string
}
const initialState: InitialState = {
loading: false,
journeys: [],
criteria: null,
error: '',
};
// generates pending, fulfilled and rejected action types
export const fetchJourneys = createAsyncThunk("user/fetchUsers", async (criteria: SearchCriteria) => {
const response = await axios.get(`${constants.api_server}/journeys?from=${criteria.from}&to=${criteria.to}&departure=${criteria.departure}`);
return response.data.journeys;
});
const journeySlice = createSlice({
name: "journey",
initialState,
reducers: {},
extraReducers: builder => {
builder.addCase(fetchJourneys.pending, (state, action) => {
console.log(action)
state.loading = true
})
builder.addCase(fetchJourneys.fulfilled, (state, action: PayloadAction<Journey[]>) => {
state.loading = false
state.journeys = action.payload
state.error = ''
})
builder.addCase(fetchJourneys.rejected, (state, action) => {
state.loading = false,
state.journeys = [],
state.error = action.error.message || 'Someting went wrong'
})
}
});
export default journeySlice.reducer
Related
I am building login system. I am having problem with store state value when I use Selector function. in my system, I want to get isSuccess, error, userInfo from store state values
/login/index.js
const userLogin = useSelector((state) => state.userLogin);
console.log(userLogin);
const { isSuccess, error, userInfo } = userLogin;
if error it will show error message on UI, otherwise if isSuccess it will show success message. isSuccess appears when post to api success (it mean user login success) and user data is dispatched to loginSuccess reduces else error value dispatched to loginFail reduces
/action/userAction.js
import {
loginSuccess,
loginFail,
} from "\~/redux/user/userLoginSlice";
export const login = (inputs) =\> async (dispatch) =\> {
try {
const {data} = await request.post("/auth/login", inputs);
dispatch(loginSuccess(data));
localStorage.setItem("userInfo", JSON.stringify(data));
} catch (error) {
console.log(error.response.data)
dispatch(loginFail(error.response.data))
}
. however instead of just getting isSuccess in loginSucces reduce, it also get error when previous login was loginFail
console.log(userLogin). it should only show userInfo and isSuccess. anyone help me to solve this problem with . this is reduces
userLoginSlice.js
import { createSlice } from '#reduxjs/toolkit';
const userLoginSlice = createSlice({
name: 'userInfo',
initialState: {},
reducers: {
loginSuccess: (state, action) => {
state.isSuccess = true;
state.userInfo = action.payload;
},
loginFail: (state, action) => {
state.error = action.payload
},
},
});
export const { loginSuccess, loginFail } = userLoginSlice.actions;
export default userLoginSlice.reducer;
and my reduce store
store.js
import { configureStore } from '#reduxjs/toolkit';
import productModalReducer from './product-modal/productModalSlice';
import cartItemsSlice from './cart/cartSlide';
import userLoginSlice from './user/userLoginSlice.js';
import userRegisterSlice from './user/userRegisterSlice.js';
import userUpdateSlice from './user/userUpdateSlice.js';
const userInfoFromStorage = localStorage.getItem('userInfo') ? JSON.parse(localStorage.getItem('userInfo')) : null;
const initialState = {
userLogin: { userInfo: userInfoFromStorage },
};
export const store = configureStore({
reducer: {
productModal: productModalReducer,
cartItems: cartItemsSlice,
userLogin: userLoginSlice,
userRegister: userRegisterSlice,
userUpdate: userUpdateSlice,
},
preloadedState: initialState,
});
State value not change when dispatch to another reduces in redux
It seems that this could be because loginSuccess is only adding userInfo and isSuccess to the state but not omitting previous error.
Perhaps try reset state value to exclude error in the reducer:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
state = { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
state = { error: action.payload };
},
},
});
Or return a state value instead of using Immer proxy state:
const userLoginSlice = createSlice({
name: "userInfo",
initialState: {},
reducers: {
loginSuccess: (state, action) => {
// 👇 Reset state to omit error message in loginSuccess
return { isSuccess: true, userInfo: action.payload };
},
loginFail: (state, action) => {
// 👇 Could add "isSuccess: false, userInfo: null" if needed
return { error: action.payload };
},
},
});
I'm fetching data from a mongoDB database and then fetch that data from the server and finally render the data to the UI in a specified component. I'm using redux-toolkit for state management.
The problem is when fetching the data from the server it is not visible in the store. Why is the empty array in the initial state still empty after fetching the data? I'm using createSlice Api that generates the action creators and action types and createAsyncThunk Api for the asynchronous task of fetching the data from the server.
Slice reducer
import { createSlice, createAsyncThunk} from '#reduxjs/toolkit'
import axios from 'axios'
const initialState = {
realestate: [],
isSuccess: false,
isLoading: false,
message: '',
}
export const getRealEstates = createAsyncThunk(
'realestate/getRealEstates', async () => {
try {
const response = await axios.get('castles')
return response.data
} catch (error) {
console.log(error)
}
}
)
export const estateSlice = createSlice({
name: 'realestate',
initialState,
reducers: {
reset: (state) => initialState,
},
extrareducers: (builder) => {
builder.addCase(getRealEstates.pending, (state) => {
state.isLoading = true
})
builder.addCase(getRealEstates.fulfilled, (state, action) => {
state.isLoading = false
state.isSuccess = true
state.realestate = action.payload
})
builder.addCase(getRealEstates.rejected, (state, action) => {
state.isLoading = false
state.isError = true
state.message = action.payload
})
}
})
export const { reset } = estateSlice.actions
export default estateSlice.reducer
Store
export const store = configureStore({
reducer: {
realestate: realestateReducer,
registered: registerReducer,
},
});
Component
const realestates = useSelector(state => state.realestate)
const { isLoading, realestate, isError, message, isSuccess} = realestates
const dispatch = useDispatch()
useEffect(() => {
dispatch(getRealEstates())
if(realestate){
setShow(true)
}else{
console.log('No data retrieved')
}
}, [dispatch, isError, realestate, message])
It's extraReducers with an uppercase R, your code contains extrareducers.
I have separated slices for every service from backend to control different states like loading and error etc., but don't know if there is better way to structure slices.
File structure for now looks like this:
featureName/service1Slice;
featureName/service2Slice;
featureName/service3Slice;
feature2Name/service1Slice;
etc...
And slice itself:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import { IContract} from 'contracts';
import { ServiceName } from 'services';
interface IAllStatuses {
loading: boolean;
whoseDataIsLoading?: string;
error: boolean;
errorText?: string | unknown;
}
const initialState: IAllStatuses = {
loading: false,
whoseDataIsLoading: '',
error: false,
errorText: '',
};
export const serviceName = createAsyncThunk('servicename', async (request: IContract) => {
const response = await ServiceName(request);
return response;
});
export const serviceNameSlice = createSlice({
name: 'service-name',
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(serviceName.fulfilled, (state) => {
state.loading = false;
});
builder.addCase(serviceName.pending, (state, action) => {
state.loading = true;
state.whoseDataIsLoading = action.payload;
});
builder.addCase(serviceName.rejected, (state, action) => {
state.error = true;
state.loading = false;
state.errorText = action.payload;
});
},
});
export const serviceNameReducer= serviceNameSlice.reducer;
I created a website and use redux-toolkit
but I have an issue with createSlice. my two records data receive correctly. but when I set data into adapter just the first record was added. this is my slice code
import { createEntityAdapter, createSlice, PayloadAction, createSelector, createAsyncThunk } from "#reduxjs/toolkit";
import { fetchTop10Podcasts } from "../../../services/endpoints/Podcasts";
import { podcastState } from "../../initialStates/Podcasts";
const podcastAdapter = createEntityAdapter();
// interface podcastState {
// entities: Array<Podcast>
// loading: 'idle' | 'loading' | 'successed' | 'failed'
// }
export const PodcastSlice = createSlice({
name: 'podcasts',
initialState: podcastAdapter.getInitialState({
entities: [],
loading: 'idle'
} as podcastState),
reducers: {
}, extraReducers: (builder) => {
builder.addCase(fetchTop10Podcasts.pending, (state, action) => {
state.loading = 'loading';
}).addCase(fetchTop10Podcasts.fulfilled, (state, action) => {
state.loading = 'successed';
podcastAdapter.setAll(state, action.payload.data.podcasts)
console.log('podcasts', action.payload.data.podcasts); // here I recieved two record
}).addCase(fetchTop10Podcasts.rejected, (state, action) => {
state.loading = 'failed';
})
}
});
export const {
selectById: selectPodcastById,
selectAll: selectPodcasts
} = podcastAdapter.getSelectors((state: any) => state.podcasts)
export const selectPodcastIds = createSelector(
selectPodcasts,
podcasts => podcasts.map((podcast: any) => podcast._id)
)
export default PodcastSlice.reducer;
I think the issue may be with your initial state.
When you call adapter.getInitialState(), the state structure it returns is {ids: [], entities: {}}, plus whatever additional fields you passed in.
You're providing entities: [] as an additional field. It's likely you're overwriting the original object structure as a result.
Remove that line and I think this will work.
I am trying to create a notification Component.
My notification Component is at root level and when ever user tries to login the process of the the async function is relayed to him i.e. pending fulfilled or rejected.
The Problem is that I don't know how to call notification reducer from userSlice or even if its possible is this a good way or not.
User Slice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const initialUserState = {
currentUser:null
}
export const getUser = createAsyncThunk(
'user/getUser',
async (endpoint, data) => {
return(
await axios.post(endpoint, data)
.then(res =>{
return res.data.user
})
.catch(error =>{
throw Error(error.response.data)
})
)
}
)
const userSlice = createSlice({
name: 'user',
initialState: initialUserState,
reducers:{
currentUser(state, action){
state.currentUser = action.payload
}
},
extraReducers:
(builder) => {
builder.addCase(getUser.pending, ()=>{
console.log("authing")
})
builder.addCase(getUser.fulfilled, (state, action)=>{
state.currentUser = action.payload
console.log("fulfilled")
})
builder.addCase(getUser.rejected, (state, action)=>{
console.log("failed")
alert(action.error.message)
})
}
})
export const userActions = userSlice.actions;
export default userSlice.reducer;
notificationSlice
import React from 'react'
import { useSelector } from 'react-redux'
function Notification() {
const toast = useSelector(state => state.notification)
console.log(toast)
return (
toast.active &&
<div className="notification" style={{backgroundColor:toast.backgroundColor}} >
{toast.message}
</div>
)
}
export default Notification
I want to change notification state when ever one of the extra reducer in userSlice is called
I think you are thinking about this almost exactly backwards. What you want is NOT to "call notification reducer from userSlice," but to LISTEN for userSlice actions in a notificationSlice.
I have done something like the following, which I think would work well for you:
import { createEntityAdapter, createSlice, isAnyOf } from '#reduxjs/toolkit'
const notificationsAdapter = createEntityAdapter()
const initialState = notificationsAdapter.getInitialState({
error: null,
success: null,
})
const notificationsSlice = createSlice({
name: 'notifications',
initialState,
reducers: {
clearNotifications: state => {
state.error = null
state.success = null
},
setError: (state, action) => {
state.success = null
state.error = action.payload
},
setSuccess: (state, action) => {
state.success = action.payload
state.error = null
},
},
extraReducers: builder => {
builder
.addMatcher(
isAnyOf(
getUser.fulfilled,
),
(state, action) => {
state.error = null
state.success = action.payload.message
}
)
.addMatcher(
isAnyOf(
getUser.rejected
// can add as many imported actions
// as you like to these
),
(state, action) => {
state.error = action?.payload
state.success = null
}
)
// reset all messages on pending
.addMatcher(
isAnyOf(
getUser.pending
),
(state, action) => {
state.error = null
state.success = null
}
)
},
})
export const { clearNotifications, setError, setSuccess } = notificationsSlice.actions
export default notificationsSlice.reducer
export const getErrorMsg = state => state.notifications.error
export const getSuccessMsg = state => state.notifications.success
Having added the above, you can now create a notification component that listens for
const error = useSelector(getErrorMsg)
const success = useSelector(getSuccessMsg)
and shows the messages accordingly.
Caveat:
My notificationSlice code assumes that when an action completes, there will exist a "message" object on the success payload. So, on my async thunks, if my api does not return this I must add this explicitly to the result.