Async/Await inside reducer in Redux - javascript

const authSlice = createSlice({
name: "auth",
initialState: {
value: {
fields: initialState,
},
},
reducers: {
loginUser: async (state, action) => {
state.value.fields.loading = true;
await http
.post("/login", {
email: state.value.fields.email,
password: state.value.fields.password,
})
.then((response) => {
console.log(response.data);
});
},
},
});
I'm receiving an error saying A non-serializable value was detected in the state, in the path: auth. Value: When I try to make this loginUser async.When I remove async await from the loginUser it's working.Really appreciate it if somebody could help.

You must never do any async work inside of a Redux reducer!
One of the primary rules of Redux is:
Reducers must always be 100% synchronous and "pure", with no side effects ( https://redux.js.org/style-guide/#reducers-must-not-have-side-effects )
Additionally, all state updates must be done by "dispatching an action" (like store.dispatch({type: "todos/todoAdded", payload: "Buy milk"}) ), and then letting the reducer look at that action to decide what the new state should be.
If you need to do async work, the right answer is usually to put the async logic into a "thunk", and dispatch actions based on the async results.
I'd strongly recommend going through the Redux docs tutorials to learn more about this and how to use Redux:
https://redux.js.org/tutorials/index

Related

Redux Toolkit: How to update state inside reducer after fetching data from the server?

I have a couple of reducer functions inside my checkoutSlice.js file.
Specifically inside fetchCheckout function I'm trying to fetch data (it's successfully fetching)
and store it in the state as checkout.
But for some reason it gives me this mistake: TypeError: Cannot perform 'get' on a proxy that has been revoked
on this line: .catch((err) => console.log(err));
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import Client from 'shopify-buy';
// Initializing a client to return content in the store's primary language
const client = Client.buildClient({
storefrontAccessToken: process.env.NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN,
domain: process.env.NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN,
});
const initialState = {
checkout: null,
checkoutID: null,
isCartOpen: false,
}
const checkoutSlice = createSlice({
name: 'checkout',
initialState,
reducers: {
createCheckout(state, action) {
state.checkout = client.checkout.create();
localStorage.setItem('checkout', checkout.id);
},
fetchCheckout(state, action) {
console.log(state)
console.log(action)
client.checkout
.fetch(action.payload)
.then((checkout) => {
console.log(checkout.lineItems)
state.checkout = checkout;
})
.catch((err) => console.log(err));
}
}
})
I kinda see the mistake here, I'm probably violating some of these rules:
But I don't know how to implement better decisions here.
Previously I was using Context API for state management and it worked well. Now I want to keep the same logic and keep fetching functions and the state at the same globally accessible place.
Any suggestions, recommendations on how to do it a better way?
Thank you in advance

React Redux - Invariant failed: A state mutation was detected inside a dispatch

I'm receiving this error: Invariant failed: A state mutation was detected inside a dispatch and can't seem to find a solution for it. I'm fetching user support tickets via an API and saving it to a state in order to display the ticket information. Here is my code:
const initialState: stateType = {
User: new User(),
SupportTickets: new SupportTickets()
}
const userSlice = createSlice({
name: "User",
initialState,
reducers: {
setUser: (state, {payload}) => {
state.User = payload
},
setSupportTickets: (state, {payload}) => {
state.SupportTickets.tickets = payload
}
}
})
export const {setUser, setSupportTickets} = userSlice.actions
export const userSelector = (state: stateType) => state.User
export const userSupportTicketsSelector = (state: stateType) => state.SupportTickets
export function fetchSupportTickets(userId: string, token: string) {
return async (dispatch: AppDispatch) => {
return await fetch(API_URL, {
method: 'GET',
headers: {
Authorization: `Bearer ${token}`
}
}).then(response => {
if (!response.ok) { throw response }
return response.json()
}).then(json => {
dispatch(setSupportTickets(json))
}).catch(error => {
console.log('Failed to fetch support tickets', error);
})
}
}
interface stateType {
User: User
SupportTickets: SupportTickets
}
export class SupportTickets {
tickets: SupportTicket[] = []
}
export class SupportTicket {
id: number = 0
type: string = ""
subject: string = ""
priority: string = ""
status: string = ""
created_at: string = ""
updated_at: string = ""
}
I've tried changing/moving things around without any success. Any help is greatly appreciated. Thanks in advance.
I see the problem. It looks like you're creating some kind of classes to store your data, and putting those in the Redux state:
const initialState: stateType = {
User: new User(),
SupportTickets: new SupportTickets()
}
You should never put class instances or other non-serializable values into the Redux state. Instead, you should use plain JS objects, arrays, and primitives.
The good news is that you're using Redux Toolkit's createSlice API, which will let you write "mutating" update logic that becomes a safe and correct immutable update. But, you need to be using plain JS objects in our state for that to work correctly.
I'd strongly recommend reading through the two tutorials in the Redux core docs:
The "Redux Essentials" tutorial, which teaches beginners "how to use Redux, the right way", using our latest recommended tools and practices.
The "Redux Fundamentals" tutorial, which teaches "how Redux works" from the bottom up.

Middleware to Vuex state not updating state?

I am trying to get my firestore data that I am getting to be stored in my state but it does not show up in my Vue dev tools in the state.
When I console.log() the data I am getting through the store action I can see I am getting the right data but it will not update the state.
I am using middle-ware on my home page and another page to dispatch my action in order to get the required data.
I have also used a conditional statement within the middle-ware below to try to only dispatch action when my other state variables are not null because the firestore query requires state.user
//this is check-auth middleware
export default function(context) {
// in initAuth we are forwarding it the req
context.store.dispatch('initAuth', context.req)
console.log('WE ARE GRABBING USER INFO')
context.store.dispatch('grabUserInfo', context.req)
console.log('There is already user info there')
// context.store.dispatch('currentUser')
}
We are dispatching grabUserInfo to run a action that has a firestore query in it.
grabUserInfo(vuexContext, context) {
let userInfo = []
var userRef = db.collection('users')
var query = userRef
.where('user_id', '==', vuexContext.state.user)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data())
userInfo.push(doc.data())
})
})
vuexContext.commit('setUserInfoSub', userInfo)
}
my
console.log(doc.data()) is showing
subscribers: ["noFace2"]
subscriptions: ["noFace3"]
user_id: "VbomJvANYDNe3Bek0suySs1L8oy1"
username: "noFace1"
my info should be going through a mutation and commiting to state, but it does not show up in my state vue dev tools.
setUserInfoSub(state, payload) {
state.userInfoSub = payload
}
I don't understand how the data is not ending up in my state. Here is my State and mutations.
const createStore = () => {
return new Vuex.Store({
state: {
loadedCards: [],
user: null,
username: null,
userInfoSub: [],
token: null
},
mutations: {
setCards(state, cards) {
state.loadedCards = cards
},
setUser(state, payload) {
state.user = payload
},
setUsername(state, payload) {
state.username = payload
},
setUserInfoSub(state, payload) {
state.userInfoSub = payload
},
setToken(state, token) {
state.token = token
}
Change your mutation to this:
setUserInfoSub(state, payload) {
Vue.set(state, 'userInfoSub', payload);
}
This will allow Vue's reactivity system to kick back in for the state variable reassignment.
Per #Alexander's comment, you should also move the commit() inside then() given the async nature of the Firebase query.

What is the best way to fetch api in redux?

How to write best way to fetch api resource in react app while we use redux in application.
my actions file is actions.js
export const getData = (endpoint) => (dispatch, getState) => {
return fetch('http://localhost:8000/api/getdata').then(
response => response.json()).then(
json =>
dispatch({
type: actionType.SAVE_ORDER,
endpoint,
response:json
}))
}
is it best way to fetch api?
The above code is fine.But there are few points you should look to.
If you want to show a Loader to user for API call then you might need some changes.
You can use async/await the syntax is much cleaner.
Also on API success/failure you might want to show some notification to user. Alternatively, You can check in componentWillReceiveProps to show notification but the drawback will be it will check on every props changes.So I mostly avoid it.
To cover this problems you can do:
import { createAction } from 'redux-actions';
const getDataRequest = createAction('GET_DATA_REQUEST');
const getDataFailed = createAction('GET_DATA_FAILURE');
const getDataSuccess = createAction('GET_DATA_SUCCESS');
export function getData(endpoint) {
return async (dispatch) => {
dispatch(getDataRequest());
const { error, response } = await fetch('http://localhost:8000/api/getdata');
if (response) {
dispatch(getDataSuccess(response.data));
//This is required only if you want to do something at component level
return true;
} else if (error) {
dispatch(getDataFailure(error));
//This is required only if you want to do something at component level
return false;
}
};
}
In your component:
this.props.getData(endpoint)
.then((apiStatus) => {
if (!apiStatus) {
// Show some notification or toast here
}
});
Your reducer will be like:
case 'GET_DATA_REQUEST': {
return {...state, status: 'fetching'}
}
case 'GET_DATA_SUCCESS': {
return {...state, status: 'success'}
}
case 'GET_DATA_FAILURE': {
return {...state, status: 'failure'}
}
Using middleware is the most sustainable way to do API calls in React + Redux applications. If you are using Observables, aka, Rxjs then look no further than redux-observable.
Otherwise, you can use redux-thunk or redux-saga.
If you are doing a quick prototype, then making a simple API call from the component using fetch is good enough. For each API call you will need three actions like:
LOAD_USER - action used set loading state before API call.
LOAD_USER_SUCC - when API call succeeds. Dispatch on from then block.
LOAD_USER_FAIL - when API call fails and you might want to set the value in redux store. Dispatch from catch block.
Example:
function mounted() {
store.dispatch(loadUsers());
getUsers()
.then((users) => store.dispatch(loadUsersSucc(users)))
.catch((err) => store.dispatch(loadUsersFail(err));
}

Redux mapStateToProps: dispatch action to close popup after async update

I pass state to my component as such:
const mapStateToProps = (state, ownProps) => {
return {
pageState: state.pageState
}
}
The pageState contains information on the 'page', i.e. page meta data (such as page-name), and it also receives the method that was used when an async CRUD operation has updated the page data ("PUT").
The React component is a modal window that the user calls when (s)he wants to update and save the page name.
The async PUT operation works fine and the pageState gets properly updated (no problem here), but I don't understand from where I should dispatch the "hideModal" action that is executed after the async call.
Options are:
mapStateToProps: check here if the pageState contains "PUT", then dispatch the close action;
From the render method: but this is discouraged;
From shouldComponentUpdate. However, I bumped into a strange bug when testing this; the mapStateToProps doesn't appear to update the this.props.pageState properly on the first request. It only works on the second (?). Also, I don't feel like I should put it here anyhow; shouldComponentUpdate should not have side-effects like this.
My code to connect the component from the container:
const renameModal = connect(
mapStateToProps,
mapDispatchToProps
)(RenameModal);
Any help?
For info: I use Redux-saga for the async call. This shouldn't matter though. Updates of page info can happen from different places in my app, and this specific action (closing the modal) should stay decoupled from the implementation that takes care of the server requests.
Redux-saga, listening for async update requests:
function* updatePage(action){
try{
const page = yield call(Api.updatePage, action.payload.siteSlug, action.payload.pageId, action.payload.page);
yield put({type: 'PUT_PAGE_SUCCEEDED', page});
}catch(error){
yield put({type: 'PUT_PAGE_FAILED', error});
}
}
export function* watchUpdatePage(){
yield* takeLatest('PUT_PAGE_REQ', updatePage);
}
The reducer:
const asyncReducer = (state = pageState, action) => {
switch(action.type){
case 'GET_PAGE_SUCCEEDED':
console.log("get page succeeded");
return Immutable.fromJS({
type: "GET_PAGE_SUCCEEDED",
method: "GET",
pageState: action.page
});
case 'PUT_PAGE_SUCCEEDED':
console.log("put page succeeded");
return Immutable.fromJS({
type: "PUT_PAGE_SUCCEEDED",
method: "PUT",
pageState: action.page
});
default:
return state;
}
}
Actions in redux can be handled by multiple reducers. Since your async call already dispatches the PUT_PAGE_SUCCEEDED action type when the call ends (and PUT_PAGE_REQ before it), you don't have to dispatch another action. Create a modal reducer like this:
const modalReducer = (state = false, action) => {
case 'SHOW_MODAL':
return Immutable.fromJS({
showModal: true
});
case 'PUT_PAGE_SUCCEEDED':
return Immutable.fromJS({
showModal: false
});
default:
return state;
}
}
And change mapStateToProps accordingly:
const mapStateToProps = (state, ownProps) => {
return {
pageState: state.pageState,
modalState: state.modalState
}
}
btw - I'm not really familiar with redux-saga, but I believe that it's not that different from the simple API middleware in the way in dispatches actions during the API call lifecycle. If it is....

Categories

Resources