Notistack doesn't work in RTK extraReducer - javascript

...
extraReducers: {
[adminLogin.fulfilled]: (state, { payload }) => {
const { data, navigate, enqueueSnackbar } = payload;
enqueueSnackbar(
`Login successful.`,
{ variant: 'success' }
);
localStorage.setItem('auth', JSON.stringify(payload));
state.auth = data;
state.loading = false;
state.success = true;
navigate('/dashboard');
},
[adminLogin.rejected]: (state, { payload }) => {
const { enqueueSnackbar } = payload;
state.loading = false;
state.error = payload;
enqueueSnackbar(`Login failed.`, { variant: 'error' });
}
}
Hİ all. As I mentioned in the title notistack doesnt work in RTK extraReducer or try-catch blocks. When i check enqueueSnackbar in console.log() i cant get it properly but when i try to invoke it, it doesn't work.

Related

redux toolkit state is not changing

When I send request I got my data back in json and I can see it when console.log().
But when I try to change my state, it's not changing. Can you guys please help me to understand why? Don't judge too hard I am still learning. Thank you
Here is my code
export const login = createAsyncThunk(
'auth/login',
async (user) => {
try {
const response = await loginUser(user);
console.log(response.data) /// data present
return response.data
} catch (error) {
console.log(error)
}
}
)
export const authSlice = createSlice({
name: 'auth',
initialState: { user: {}, status: '', message: '', success: false, error: '' },
reducers: {
[login.pending]: (state, action) => (
state.status = 'loading'
),
[login.fulfilled]: (state, { payload }) => {
state.user = payload.user
state.status = 'success'
state.message = payload.message
state.success = true
state.error = ''
},
[login.rejected]: (state, { payload }) => {
state.user = payload
state.status = 'failed'
state.success = false
state.error = payload
}
}
})
You need use the extraReducers with builder, something like that :
reducers: {
//something, we don't care it
},
extraReducers: (builder) => {
builder.addCase(login.pending, (state) => {
state.status = what you want
});
builder.addCase(login.fulfilled, (state) => {
blabla
});
... and again and again
},
see https://redux-toolkit.js.org/api/createAsyncThunk (all is on the doc, no better source for RDK)

Vuex: Wait for websocket response before dispatching action

So this is the scenario / premises:
In order to populate a chat queue in real time I need to open a connection to a websocket, send a message and then set the data to a websocket store. This store will basically manage all the websocket state.
Before populating the chat queue there's two parameters I need: a shiftId coming from one http API request and a connectionId coming from the websocket. Using those two parameters I finally can subscribe to a third http API and start receiving messages to populate the chat queue.
The problem is that due to the async behaviour of the websocket (or that's what I think, please feel to correct me if I'm wrong) I always get an empty "connectionId" when trying to make the put to that "subscription" API. I have tried with async/await and promises but nothing seems to work. I'm pretty new to async/await and websockets with Vuex so pretty sure I'm doing something wrong.
This is the user vuex module where I do all the login/token operations and dispatch a "updateEventsSubscription" action from the shift vuex module. In order for the "updateEventsSubscription" action to work I need to get the response from the "processWebsocket" action (to get the connectionId parameter) and from the "startShift" action (to get the shiftId parameter) coming from the shifts vuex module:
import UserService from '#/services/UserService.js'
import TokenService from '#/services/TokenService.js'
import router from '#/router'
export const namespaced = true
export const state = {
accessToken: '',
errorMessage: '',
errorState: false,
userEmail: localStorage.getItem('userEmail'),
userPassword: localStorage.getItem('userPassword'),
}
export const mutations = {
SET_TOKEN(state, accessToken) {
state.accessToken = accessToken
TokenService.saveToken(accessToken)
},
SET_USER(state, authUserJson) {
state.userEmail = authUserJson.email
state.userPassword = authUserJson.password
localStorage.setItem('userPassword', authUserJson.password)
localStorage.setItem('userEmail', authUserJson.email)
},
SET_ERROR(state, error) {
state.errorState = true
state.errorMessage = error.data.error_description
},
CLOSE_NOTIFICATION(state, newErrorState) {
state.errorState = newErrorState
},
}
export const actions = {
signIn({ commit, dispatch, rootState }, authUserJson) {
return UserService.authUser(authUserJson)
.then((result) => {
commit('SET_USER', authUserJson)
commit('SET_TOKEN', result.data.access_token)
dispatch('token/decodeToken', result.data.access_token, {
root: true,
})
dispatch(
'shifts/updateEventsSubscription',
rootState.token.agentId,
{
root: true,
}
)
router.push('/support')
})
.catch((error) => {
console.log(error)
if (error.response.status === 400) {
commit('SET_TOKEN', null)
commit('SET_USER', {})
commit('SET_ERROR', error.response)
} else {
console.log(error.response)
}
})
},
signOut({ commit }) {
commit('SET_TOKEN', null)
commit('SET_USER', {})
localStorage.removeItem('userPassword')
localStorage.removeItem('userEmail')
TokenService.removeToken()
router.push('/')
},
closeNotification({ commit }, newErrorState) {
commit('CLOSE_NOTIFICATION', newErrorState)
},
}
export const getters = {
getToken: (state) => {
return state.accessToken
},
errorState: (state) => {
return state.errorState
},
errorMessage: (state) => {
return state.errorMessage
},
isAuthenticated: (state) => {
return state.accessToken
},
userEmail: (state) => {
return state.userEmail
},
userPassword: (state) => {
return state.userPassword
},
}
This is websocket store: I pass the connectionId to the state in order to be able to use it in another vuex action to subscribe for new chats:
export const namespaced = true
export const state = {
connected: false,
error: null,
connectionId: '',
statusCode: '',
incomingChatInfo: [],
remoteMessage: [],
messageType: '',
ws: null,
}
export const actions = {
processWebsocket({ commit }) {
const v = this
this.ws = new WebSocket('mywebsocket')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
},
}
export const mutations = {
SET_REMOTE_DATA(state, remoteData) {
const wsData = JSON.parse(remoteData.data)
if (wsData.connectionId) {
state.connectionId = wsData.connectionId
console.log(`Retrieving Connection ID ${state.connectionId}`)
} else {
console.log(`We got chats !!`)
state.messageType = wsData.type
state.incomingChatInfo = wsData.documents
}
},
SET_CONNECTION(state, message) {
if (message == 'open') {
state.connected = true
} else state.connected = false
},
SET_ERROR(state, error) {
state.error = error
},
}
And finally this is the shift store (where the problem is), as you can see I have a startShift action (everything works fine with it) and then the "updateEventsSubscription" where I'm trying to wait for the response from the "startShift" action and the "processWebsocket" action. Debugging the app I realize that everything works fine with the startShift action but the websocket action sends the response after the "updateEventsSubscription" needs it causing an error when I try to make a put to that API (because it needs the connectionId parameter coming from the state of the websocket).
import ShiftService from '#/services/ShiftService.js'
export const namespaced = true
export const state = {
connectionId: '',
shiftId: '',
agentShiftInfo: '{}',
}
export const actions = {
startShift({ commit }, agentId) {
return ShiftService.startShift(agentId)
.then((response) => {
if (response.status === 200) {
commit('START_SHIFT', response.data.aggregateId)
}
})
.catch((error) => {
console.log(error)
if (error.response.status === 401) {
console.log('Error in Response')
}
})
},
async updateEventsSubscription({ dispatch, commit, rootState }, agentId) {
await dispatch('startShift', agentId)
const shiftId = state.shiftId
await dispatch('websocket/processWebsocket', null, { root: true })
let agentShiftInfo = {
aggregateId: state.shiftId,
connectionId: rootState.websocket.connectionId,
}
console.log(agentShiftInfo)
return ShiftService.updateEventsSubscription(shiftId, agentShiftInfo)
.then((response) => {
commit('UPDATE_EVENTS_SUBSCRIPTION', response.data)
})
.catch((error) => {
if (error.response.status === 401) {
console.log('Error in Response')
}
})
},
}
export const mutations = {
START_SHIFT(state, shiftId) {
state.shiftId = shiftId
console.log(`Retrieving Shift ID: ${state.shiftId}`)
},
UPDATE_EVENTS_SUBSCRIPTION(state, agentShiftInfo) {
state.agentShiftInfo = agentShiftInfo
},
}
You should convert your WebSocket action into a promise that resolves when WebSocket is connected.:
export const actions = {
processWebsocket({ commit }) {
return new Promise(resolve=> {
const v = this
this.ws = new WebSocket('mywebsocket')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
resolve();
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
});
},
}
So I realized that I have to resolve the promise on the this.ws.message instead. By doing that all my data is populated accordingly, there's still sync issues (I can't feed the websocket state at the moment because due to its async behaviour the state is not there yet when other components try to use it via: rootGetters.websocket.incomingChats for example) but I guess that's part of another question. Here's the final version of the module action:
export const actions = {
processWebsocket({ commit }) {
return new Promise((resolve) => {
const v = this
this.ws = new WebSocket('wss://ws.rubiko.io')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
resolve(event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
})
},
}
Anyways, thanks #Eldar you were in the right path.

i cant catch status and json values from createAsyncThunk function

usersSlice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import { API } from "../axios/index";
export const signUp = ...
export const logOut = ...
export const signIn = createAsyncThunk("users/signin", async (params) => {
try {
const { loginData, history } = params;
const { data } = await API.post("users/signin", loginData);
history.push("/");
return data;
} catch (error) {
console.log(error);
}
});
const initialState = {
usersInfo: {},
status: "idle",
error: null,
existEmail: false,
};
const usersSlice = createSlice({
name: "users",
initialState,
reducers: {
handleExistEmail: (state, action) => {
state.existEmail = action.payload;
},
},
extraReducers: {
...
[signIn.fulfilled]: (state, action) => {
console.log("here is your data : ", action.payload);
state.status = "succeeded";
if (action.payload) {
localStorage.setItem("user", JSON.stringify(action.payload));
}
},
},
});
export default usersSlice.reducer;
export const { handleExistEmail } = usersSlice.actions;
userRouter.js
const isPasswordCorrent = await bcrypt.compare(password, user.password);
if (!isPasswordCorrent) {
return res
.status(404)
.json({ message: "Password dont match" });
}
Hi all.When password and re-passwordn dont match i want to backend send me status(404) and json({ message: "Password dont match" }) values and i want to catch these values in [signIn.fulfilled] but action.payload send me undefined.But if i do return res.json({ message: "Password dont match" }) instead of return.status(404).json({message: "Password dont match"}) this time i cant catch json({message: "Password dont match"}) from [signIn.fulfilled].Why i have to delete .status(404) part to dont get undefined ?
This is how the createAsyncThunk works. This wrapper itself is a try/catch block, so doesn't make sense to use in this action creator function. If a promise is rejected in this creator function body, then your action returns a rejected sub-action in the store. So you have to listen to this action in the reducer. Or if you really want to use a try/catch block, then in the catch block throw the error. A little example usage:
export const exampleAsyncAction = createAsyncThunk(
ACTION_TYPE,
async (parameter) => {
const result = await apicall(parameter);
return result.doSomeLogic();
}
);
const reducer = createReducer(
...,
[exampleAsyncAction.pending]: (state) => {
state.loading = true;
state.error = null;
},
[exampleAsyncAction.fulfilled]: (state, {payload}) => {
state.result = payload;
state.loading = false;
},
[exampleAsyncAction.rejected]: (state, {error}) => {
state.error = error;
state.loading = false;
},
)

React can't read value gotten from Redux state

Bit of a weird one. In my component I am getting a "task" object from my "taskDetails" reducer. Then I have a useEffect function that checks if the task.title is not set, then to call the action to get the task.
However when the page loads I get an error cannot read property 'title' of null which is strange because when I look in my Redux dev tools, the task is there, all its data inside it, yet it can't be retrieved.
Here is the relevant code:
const taskId = useParams<{id: string}>().id;
const dispatch = useDispatch();
const history = useHistory();
const [deleting, setDeleting] = useState(false)
const taskDetails = useSelector((state: RootStateOrAny) => state.taskDetails);
const { loading, error, task } = taskDetails;
const successDelete = true;
const deleteTaskHandler = () => {
}
useEffect(() => {
if(!successDelete) {
history.push('/home/admin/tasks')
} else {
if(!task.title || task._id !== taskId) {
dispatch(getTaskDetails(taskId))
}
}
},[dispatch, task, taskId, history, successDelete])
REDUCER
export const taskDetailsReducer = (state = { task: {} }, action) => {
switch(action.type) {
case TASK_DETAILS_REQUEST:
return { loading: true }
case TASK_DETAILS_SUCCESS:
return { loading: false, success: true, task: action.payload }
case TASK_DETAILS_FAIL:
return { loading: false, error: action.payload }
case TASK_DETAILS_RESET:
return { task: {} }
default:
return state
}
}
ACTION
export const getTaskDetails = id => async (dispatch) => {
try {
dispatch({
type: TASK_DETAILS_REQUEST
})
const { data } = await axios.get(`http://localhost:5000/api/tasks/${id}`)
dispatch({
type: TASK_DETAILS_SUCCESS,
payload: data
})
} catch (error) {
dispatch({
type: TASK_DETAILS_FAIL,
payload:
error.response && error.response.data.message
? error.response.data.message
: error.message
})
}
}
In my reducer in the TASK_DETAILS_REQUEST case, I just had loading: false.
I had failed to specify the original content of the state, I did this by adding ...state.
export const taskDetailsReducer = (state = { task: {} }, action) => {
switch(action.type) {
case TASK_DETAILS_REQUEST:
return { ...state, loading: true }
case TASK_DETAILS_SUCCESS:
return { loading: false, success: true, task: action.payload }
case TASK_DETAILS_FAIL:
return { loading: false, error: action.payload }
case TASK_DETAILS_RESET:
return { task: {} }
default:
return state
}
}

redux to redux-toolkit refactoring

can somebody explain why this code dispatching 'actions.loginSuccess' when i get 401 error from server ?
isn't it should go to 'catch' part of axios request ?
Before i did it without redux toolkit features
const login = ({username, password}) => async dispatch => {
await axios.post(`${API_URL}/token/`, {username, password})
.then(response => {
dispatch(actions.loginSuccess({ client_id: response?.data.client_id }))
history.push('/')
})
.catch(e => {
dispatch(actions.loginError({ error: String(e) }))
})
}
//actions.js
const login = createAction('#USER/login')
const loginSuccess = createAction('#USER/login-success')
const loginError = createAction('#USER/login-error')
export const actions = {
login,
loginSuccess,
loginError
}
//reducers.js
export const userReducer = createReducer(initialState, builder => {
builder.addCase(actions.login, draft => {
draft.loading = true
})
builder.addCase(actions.loginSuccess, (draft, action) => {
draft.loading = false
draft.isLoggedIn = true
draft.data = { ...draft.data, client_id : action.client_id}
})
builder.addCase(actions.loginError, (draft, action) => {
draft.loading = false
draft.error = action.payload.error
draft.isLoggedIn = false
draft.isSignedup = false
})
}
can somebody explain why this code dispatching 'actions.loginSuccess'
when i get 401 error from server ? isn't it should go to 'catch' part
of axios request ?
// there's a difference beetween HTTP Status Code and Server Response Body Code.
// if HTTP status code is not 200, it should dispatched loginError()
// if HTTP status code is 200, and theres a response body JSON
// e.g
const resp = {
statusCode: 401,
message: 'unauthorized',
}
// You must make if conditions to handle that error code
Here's redux-toolkit version of your code to handle either HTTP status code 401, or body response code
// import axios & history
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
data: {},
loading: false,
isLoggedIn: false,
isSignedup: false,
};
// Reducers
const userSlice = createSlice({
name: '#USER',
initialState: initialState,
reducers: {
loginStart(state) {
state.loading = true;
},
loginSuccess(state, action) {
state.data = {
...state.data,
client_id: action.payload.client_id
};
state.loading = false;
state.isLoggedIn = true;
},
loginError(state, action) {
state.loading = false;
state.error = action.payload.error;
state.isLoggedIn = false;
state.isSignedup = false;
},
},
});
// actions
export const { loginStart, loginSuccess, loginError } = userSlice.actions;
export default userSlice.reducer;
export const login = ({ username, password }) => async (dispatch) => {
dispatch(loginStart());
try {
const response = await axios.post(`${API_URL}/token/`, {
username,
password,
});
if(response && response.statusCode !== 200){
return dispatch(loginError({ error: response.message }));
}
dispatch(loginSuccess({ client_id: response?.data.client_id }));
history.push('/');
} catch (e) {
dispatch(loginError({ error: String(e) }));
}
};
don't forget to add userSlice into configureStore()
const reducer = {
"#USER": userReducers, //got from export default userSlice.reducer
};
export default configureStore({
reducer,
middleware,
devTools: process.env.NODE_ENV !== 'production',
});

Categories