createAsyncThunk from Redux Toolkit yields undefined payload when fetching data from Firebase - javascript

I am using createAsyncThunk API from Redux Toolkit when fetching notes data from Google Firebase which stores in collection notes
In notebookSlice.js I define the functional thunk and slice
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const firebase = require('firebase');
export const fetchNotes = createAsyncThunk(
'users/fetchNotes',
async () => {
firebase.firestore().collection('notes').get()
.then((snapshot) => {
var data = [];
snapshot.forEach((doc) => {
data.push({
title: doc.data().title,
body: doc.data().body,
id: doc.id
})
});
console.log(data); // not null
return data;
})
.catch((err) => {
console.log(err)
});
}
)
export const notebookSlice = createSlice({
name: 'notebook',
initialState: {
selectedNoteIndex: null,
selectedNote: null,
notes: null,
count: 3,
loadingNotes: false,
error: null
},
reducers: {
...
},
extraReducers: {
[fetchNotes.pending]: (state, action) => {
if (state.loadingNotes === false) {
state.loadingNotes = true
}
},
[fetchNotes.fulfilled]: (state, action) => {
if (state.loadingNotes === true) {
state.notes = action.payload;
console.log(action.payload); // null
state.loadingNotes = false;
}
},
[fetchNotes.rejected]: (state, action) => {
if (state.loadingNotes === true) {
state.loadingNotes = false;
state.error = action.payload;
}
}
}
And I use them in component sidebar.js
import React, {useState, useEffect} from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchNotes } from './notebookSlice';
export function Sidebar(props) {
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchNotes());
})
return (
...
)
}
I am pretty sure that I get complete data from the thunk function but the state.notes remains null after fetching the data with a final fulfilled status. What's wrong with my code?

In fetchNotes, you declare a promise but not returning any value from the function itself, so basically its a javascript issue and not related Redux/React.
export const fetchNotes = createAsyncThunk("users/fetchNotes", async () => {
// Returns data after resolve
const data = await firebasePromise();
return data;
});
Your current code returns a promise, you need to resolve it at some point.
export const fetchNotes = createAsyncThunk("users/fetchNotes", async () => {
const promise = firebase
.firestore()
.collection("notes")
.get()
.then((snapshot) => {
const data = [];
// assign data
return data;
});
const data = await promise;
return data;
});
//
Read more about promises in MDN docs.

Related

Why State won't Change When I try to dispatch in getServerSideProps? Redux-Next-Wrapper

When I Try to dispatch in getServerSideProps the Redux Store won't change
When i Console.log the store After Dispatch I see the changes in console but when the page load the Store is empty array..
Why Changes won't effect?
createSlice
import { createSlice } from "#reduxjs/toolkit";
import { Store } from "../../types/type";
const { actions, reducer } = createSlice({
name: "dashboard",
initialState: { users: [], roles: [], ads: [], category: [] },
reducers: {
SET_ROLES: (store, { payload }) => {
store.roles = payload;
return store;
},
SET_USERS: (store, { payload }) => {
store.users = payload;
return store;
},
SET_ADS: (store, { payload }) => {
store.ads = payload;
return store;
},
SET_CATEGORY: (store, { payload }) => {
store.category = payload;
return store;
},
},
});
// Selector
export const selectDashboard = (store: Store) => store.entities.dashboard;
export const { SET_ROLES, SET_ADS, SET_USERS, SET_CATEGORY } = actions;
export default reducer;
Page
export const getServerSideProps = wrapper.getServerSideProps(
(store) => async (context) => {
const { data: ads } = await axios.get(endPoint);
const { data: users } = await axios.get(endPoint);
const { data: roles } = await axios.get(endPoint);
const { data: categories } = await axios.get(endPoint);
console.log("Before DISPATCH", store.getState());
store.dispatch(SET_USERS(users));
store.dispatch(SET_ADS(ads));
store.dispatch(SET_CATEGORY(categories));
store.dispatch(SET_ROLES(roles));
console.log("After DISPATCH", store.getState()); // I Can See The Changes In Console
return {
props: {},
};
}
);
The state set in the server will get cleared when dehydrations happen. You need to update server state with client state.
const reducer = (
state: ReturnType<typeof combinedReducer> | undefined,
action: AnyAction
) => {
if (action.type === HYDRATE) {
const nextState = {
...state, // use previous state
...action.payload, // apply delta from hydration
};
return nextState;
} else {
return combinedReducer(state, action);
}
};
export const store = configureStore({
reducer,
devTools: process.env.NODE_ENV !== 'production',
middleware: (getDefaultMiddleware) =>
....

POST request response undefined , but REQUEST works

userSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import LoginService from "../../Services/Login.service";
export const userRegister = createAsyncThunk(
"users/register",
async (params) => {
try {
const { registerForm } = params;
const { data } = await LoginService.register(registerForm);
return data;
} catch (error) {
}
}
);
const initialState = {
userData: {},
errorResponse: null,
status: "idle",
};
export const userSlice = createSlice({
name: "User",
initialState,
reducers: {},
extraReducers: {
[userRegister.pending]: (state, action) => {
state.status = "loading";
},
[userRegister.fulfilled]: (state, action) => {
state.status = "succeeded";
state.userData = action.payload;
},
[userRegister.error]: (state, action) => {
state.status = "failed";
state.errorResponse = action.payload;
},
},
});
export default userSlice.reducer;
Login.service.js
import axios from "axios";
const API = axios.create({ baseURL: 'http://localhost:3001'});
const LoginService = {
register: async (registerData ) => {
await API.post('/users/register', registerData)
}
};
export default LoginService;
Hi.I try add register feature to my app. But when i submit register form, the datas is saved to the database without any problems. But this line const data = await LoginService.register(registerForm); doesnt work data is undefined but when i same post request in postman i get response data the way i want.
LoginService.register is not returning anything,
you can fix that by doing:
const LoginService = {
register: async (registerData ) => {
const response = await API.post('/users/register', registerData);
return response.data;
}
};

Redux Toolkit createSlice in React

I'm learning Redux-Saga and everything works well with { configureStore, getDefaultMiddleware, createAction, createReducer }. However, I cannot successfully implement createSlice.
My actions seem to be dispatched just fine (though I'm not sure since I have multiple Redux stores and placing console.log inside createSlice doesn't seem to work...). I just cannot get the store values - after dispatched action the relevant state value (initially '') becomes undefined. I did wrap my component inside Provider and all. Can someone enlighten me how does createSlice work? Thanks.
RESOLVED I had a bug somewhere else in my code, that's why the reducers weren't working proberly. BUT what I was asking about and what was causing my problems is this: actions passed to createSlice must be 'pure' functions, meaning: (state, action) -> state, nothing fancy. That's why I had to remove my fetching functions (getData1 and getData2) from this createSlice.
ComponentWrapper returns this
<Provider store={toolkitCreateSliceStore}>
<ReduxToolkitCreateSliceComponent />
</Provider>
Component (Buttons just dispatch actions)
class ReduxToolkitCreateSliceComponent extends React.Component {
render () {
return (
<>
<h2>
{this.props.data1}
{(this.props.data1!=='' && this.props.data2!=='') ? ', ' : ''}
{this.props.data2}
</h2><br/>
<h3>{this.props.message}</h3>
<Button1 />
<Button2 />
<Button3 />
</>
);
}
}
function mapStateToProps(state) {
return {
data1: state.toolkitCreateSliceReducer.data1,
data2: state.toolkitCreateSliceReducer.data2,
message: state.toolkitCreateSliceReducer.message
};
}
export default connect(mapStateToProps)(ReduxToolkitCreateSliceComponent);
Redux Toolkit slice
import { createSlice } from "#reduxjs/toolkit";
import axios from "axios";
const initialSliceState = {
data1: '',
data2: '',
message: ''
};
const slice = createSlice({
name: "slice",
initialState: initialSliceState,
reducers: {
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
getData2: (state, action) => {
return dispatch => {
dispatch(loading2());
return axios.get('http://localhost:8081/data2')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse2(response.data));
}
}).catch(error => dispatch(displayError2(error)));
};
},
setResponse1: (state, action) => {
state.data1 = action.payload;
state.message = 'success';
},
setResponse2: (state, action) => {
state.data2 = action.payload;
state.message = 'success';
},
reset: (state, action) => {
state.data1 = '';
state.data2 = '';
state.message = 'reset';
},
loading1: (state, action) => {
state.message = 'loading';
},
loading2: (state, action) => {
state.message = 'loading';
},
displayError1: (state, action) => {
state.message = action.payload;;
},
displayError2: (state, action) => {
state.message = action.payload;;
}
}
});
export const toolkitCreateSliceReducer = slice.reducer;
const { getData1, getData2, setResponse1, setResponse2, reset, loading1, loading2,
displayError1, displayError2} = slice.actions;
export default slice;
Redux Toolkit store
const middleware = [
...getDefaultMiddleware()
];
const toolkitCreateSliceStore = configureStore({
reducer: {
toolkitCreateSliceReducer
},
middleware
});
export default toolkitCreateSliceStore;
Your "reducers" are very wrong.
A reducer must never have any side effects like AJAX calls.
You've written some Redux "thunk" functions where your reducers should be:
getData1: (state, action) => {
return dispatch => {
dispatch(loading1());
return axios.get('http://localhost:8081/data1')
.then(function (response) {
if (response.status === 200) {
dispatch(setResponse1(response.data));
}
}).catch(error => dispatch(displayError1(error)));
};
},
This is a thunk, not a reducer.
A reducer would be something like:
getData(state, action) {
return action.payload;
}
I'd specifically recommend reading through our brand-new "Redux Essentials" core docs tutorial, which teaches beginners "how to use Redux, the right way", using our latest recommended tools and practices like Redux Toolkit. It specifically covers how reducers should work, how to write reducers with createSlice, and how to write and use thunks alongside createSlice:
https://redux.js.org/tutorials/essentials/part-1-overview-concepts
If you're interested in creating async actions, let me recommend you an npm package that I created and use. It is saga-toolkit that allows async functions to get resolved by sagas.
slice.js
import { createSlice } from '#reduxjs/toolkit'
import { createSagaAction } from 'saga-toolkit'
const name = 'example'
const initialState = {
result: null,
loading: false,
error: null,
}
export const fetchThings = createSagaAction(`${name}/fetchThings`)
const slice = createSlice({
name,
initialState,
extraReducers: {
[fetchThings.pending]: () => ({
loading: true,
}),
[fetchThings.fulfilled]: ({ payload }) => ({
result: payload,
loading: false,
}),
[fetchThings.rejected]: ({ error }) => ({
error,
loading: false,
}),
},
})
export default slice.reducer
sagas.js
import { call } from 'redux-saga/effects'
import { takeLatestAsync } from 'saga-toolkit'
import API from 'hyper-super-api'
import * as actions from './slice'
function* fetchThings() {
const result = yield call(() => API.get('/things'))
return result
}
export default [
takeLatestAsync(actions.fetchThings.type, fetchThings),
]

Redux Toolkit and Axios

I'm using Redux Toolkit to connect to an API with Axios.
I'm using the following code:
const products = createSlice({
name: "products",
initialState: {
products[]
},
reducers: {
reducer2: state => {
axios
.get('myurl')
.then(response => {
//console.log(response.data.products);
state.products.concat(response.data.products);
})
}
}
});
axios is connecting to the API because the commented line to print to the console is showing me the data. However, the state.products.concat(response.data.products); is throwing the following error:
TypeError: Cannot perform 'get' on a proxy that has been revoked
Is there any way to fix this problem?
I would prefer to use createAsyncThunk for API Data with extraReducers
Let assume this page name is productSlice.js
import { createSlice,createSelector,PayloadAction,createAsyncThunk,} from "#reduxjs/toolkit";
export const fetchProducts = createAsyncThunk(
"products/fetchProducts", async (_, thunkAPI) => {
try {
//const response = await fetch(`url`); //where you want to fetch data
//Your Axios code part.
const response = await axios.get(`url`);//where you want to fetch data
return await response.json();
} catch (error) {
return thunkAPI.rejectWithValue({ error: error.message });
}
});
const productsSlice = createSlice({
name: "products",
initialState: {
products: [],
loading: "idle",
error: "",
},
reducers: {},
extraReducers: (builder) => {
builder.addCase(fetchProducts.pending, (state) => {
state. products = [];
state.loading = "loading";
});
builder.addCase(
fetchProducts.fulfilled, (state, { payload }) => {
state. products = payload;
state.loading = "loaded";
});
builder.addCase(
fetchProducts.rejected,(state, action) => {
state.loading = "error";
state.error = action.error.message;
});
}
});
export const selectProducts = createSelector(
(state) => ({
products: state.products,
loading: state.products.loading,
}), (state) => state
);
export default productsSlice;
In your store.js add productsSlice: productsSlice.reducer in out store reducer.
Then for using in component add those code ... I'm also prefer to use hook
import { useSelector, useDispatch } from "react-redux";
import {
fetchProducts,
selectProducts,
} from "path/productSlice.js";
Then Last part calling those method inside your competent like this
const dispatch = useDispatch();
const { products } = useSelector(selectProducts);
React.useEffect(() => {
dispatch(fetchProducts());
}, [dispatch]);
And Finally, you can access data as products in your component.
It is happening because your reducer function is not a pure function, it should not be having any asynchronous calls.
something like this should work. it will fix the error you are getting
const products = createSlice({
name: "products",
initialState: {
products: []
},
reducers: {
reducer2: (state, { payload }) => {
return { products: [...state.products, ...payload]}
})
}
}
});
and api should be called outside
const fetchProducts = () => {
axios.get('myurl')
.then(response => {
//console.log(response.data.products);
store.dispatch(products.actions.reducer2(response.data.products))
})
};
PS: haven't tried running above code, you may have to make modifications as per your need.

Reducer in react/redux app only working for one action.type

In my store.js i have the following code:
import { createStore, applyMiddleware} from 'redux';
import thunk from 'redux-thunk'
const reducer = (state, action) => {
console.log(action.type)
if (action.type === 'LOAD_USERS') {
return {
...state,
users: action.users['users']
}
} else if (action.type === 'LOAD_CHATROOMS') {
return {
...state,
chatRooms: action.chatRooms['chatRooms']
}
}
return state;
}
export default createStore(reducer, {users:[], chatRooms:[]}, applyMiddleware(thunk));
the code inside the action.type === 'LOAD_CHATROOMS' is never accessed for some reason, this is the action file where i set the action type for the reducer:
import axios from 'axios'
axios.defaults.withCredentials = true
const loadUsers = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_USERS',
users: response.data
});
});
};
};
const logIn = user => {
return axios.post('http://localhost:3000/session', {
user_id: user.id
})
.then(response => {
//TODO do something more relevant
console.log('loged in');
});
};
const loadChatRooms = () => {
return dispatch => {
return axios.get('http://localhost:3000/session/new.json')
.then(response => {
dispatch({
type: 'LOAD_CHATROOMS',
chatRooms: response.data
});
});
};
};
const enterChatRoom = chatrom => {
};
export { loadUsers, logIn, enterChatRoom, loadChatRooms};
The 'Load methods' get the data that i use to populate both components (one for users list and the other one for chatrooms list ), both components are called at the same level in the app.js file.
Basically the output that i'm getting is the first component (users) as expected with the correct list, and the chatrooms component is also rendered but the data is not loaded (since it's corresponding reducer block is not accessed).
Thanks a lot for reading :)

Categories