I am trying to implement Redux Toolkit and Redux Saga with Firebase. Currently I am stuck with the problem of fetching the data from the Firestore. I have configured the store and everything that is needed for the Redux Saga and Toolkit, now the only problem I have is when I try to fetch the data from the Firestore. I have made also the helper functions which can and already did help me, but just without the Redux involved.
This is what I mean by this:
store.js
import createSagaMiddleware from "redux-saga";
import { configureStore } from "#reduxjs/toolkit";
import allQuestionsReducer from "./allQuestionsState";
import allQuestionsSaga from "./redux-saga/allQuestionsSaga";
const saga = createSagaMiddleware();
const store = configureStore({
reducer: {
allQuestions: allQuestionsReducer,
},
middleware: [saga],
});
saga.run(allQuestionsSaga);
export { store };
AllQuestionsState.js
import { createSlice } from "#reduxjs/toolkit";
export const allQuestionSlice = createSlice({
name: "allQuestions",
initialState: {
allQuestions: [],
isLoading: false,
error: null,
},
reducers: {
getAllQuestionsFetch: (state, action) => {
state.isLoading = true;
},
getAllQuestionsSuccess: (state, action) => {
state.allQuestions = action.payload;
state.isLoading = false;
},
getAllQuestionsError: (state, action) => {
state.error = action.payload;
state.isLoading = false;
},
},
});
export const {
getAllQuestionsFetch,
getAllQuestionsSuccess,
getAllQuestionsError,
} = allQuestionSlice.actions;
export default allQuestionSlice.reducer;
AllQuestionsSaga.js
import { call, put, takeEvery } from "redux-saga/effects";
import { getQuestions } from "../../utils/firebase-functions/firebase-functions";
import { getAllQuestionsSuccess } from "../allQuestionsState";
function* workGetAllQuestions() {
const response = yield new Promise(getQuestions);
yield put(getAllQuestionsSuccess(response));
}
function* allQuestionsSaga() {
yield takeEvery("allQuestions/getAllQuestionsFetch", workGetAllQuestions);
}
export default allQuestionsSaga;
Helper Function
export const getQuestions = async (formData) => {
let error;
try {
const success = await onSnapshot(collection(firebaseDatabase, "questions"));
return success.docs.map((doc) => ({ ...doc.data(), id: doc.id }));
} catch (error) {
error = error.message;
return error;
}
};
Where the action is dispatched
const dispatch = useDispatch();
const data = useSelector(
(state) => state.allQuestions.allQuestions
);
useEffect(() => {
dispatch(getAllQuestionsFetch());
}, []);
console.log(data);
Does anyone has any clue on how to fix this. I am getting the [] even though I have data in Firestore.
Related
I am trying to add redux to Next.js. It is not working Ok
This is my reducerSlice file
import { createSlice } from '#reduxjs/toolkit';
import { HYDRATE } from 'next-redux-wrapper';
import { AppState } from '..';
export const ProfileSlice = createSlice({
name: 'profile',
initialState: {
name: [] as any
},
reducers: {
setProfileData: (state, action) => {
state.name = [...state.name, action.payload];
}
},
extraReducers: {
[HYDRATE]: (state, action) => {
if (!action.payload.profile.name) {
return state;
}
const nextState = {
...state, // use previous state
name: [...state.name, ...action.payload.profile.name]
};
return nextState;
}
}
});
export const { setProfileData } = ProfileSlice.actions;
export const selectProfile = (state:AppState)=>state.profile;
export default ProfileSlice.reducer;
When I use server side rendering to dispatch data to the store a single value is stored okay but when i use array the hydration is called multiple times
You can see in this image Emma is logged 4 times
I don't know what to do
Here is how I used SSR in profile file
export const getServerSideProps = wrapper.getServerSideProps(store => async ({ query }) => {
console.log('store state on the server before dispatch', store.getState());
const response = await fetch(
`https://reqres.in/api/users/${Math.floor(Math.random() * 10 + 1)}`
);
const { data } = await response.json();
const data2 = data.first_name;
store.dispatch(setProfileData(`${data.first_name}`));
return {
props: {
data2
}
};
});
I am trying to auth session by random user with http get request and createAsyncThunk.
fetching the user data in App.js on mount hook.
I can see the get request in my network and the new fetched state in redux dev tool,
but my TopBar.js useSelector return the initial state before the fetch.
TopBar.js user log:
App.js:
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchRandomUserData())
}, [dispatch]);
authSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
const initialState = {
isLoggedIn: true,
user: {},
loading: false,
error: null,
};
export const fetchRandomUserData = createAsyncThunk(
'auth/fetchRandomUser',
async () => {
try {
const response = await fetch('https://randomuser.me/api/');
const data = await response.json();
return data.results[0];
} catch (error) {
throw Error(error);
}
}
);
const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
logout(state, action) {
state.isLoggedIn = false;
},
},
extraReducers: {
[fetchRandomUserData.pending]: (state, action) => {
state.loading = true;
state.error = null;
},
[fetchRandomUserData.fulfilled]: (state, action) => {
console.log("action.payload",action.payload);
state.user = action.payload;
state.loading = false;
},
[fetchRandomUserData.rejected]: (state, action) => {
state.error = action.error.message;
state.loading = false;
},
},
});
export const { logout } = authSlice.actions;
export default authSlice.reducer;
store.js
import { configureStore } from '#reduxjs/toolkit';
// import booksReducer from './reducers/booksReducer';
import booksReducer from './slices/bookSlice';
import authReducer from './slices/authSlice';
const store = configureStore({
reducer: { books: booksReducer, auth: authReducer },
});
export default store;
TopBat.js:
export default function TopBar(): JSX.Element {
const user = useSelector((state: any) => state.auth);
console.log("topbar",user); // returns the initial state
//....
Please make sure that you update react-redux to version 8 if you are using react 18.
There are known cases where components just stop updating if you are using react-redux 7 and lower with react 18.
Please help me how I can introduce new function like getOrdersByCustomer in ordersSlice. I have provided full code of ordersSlice below. Please tell me what is extraReducers and how it works.
import { createSlice, createAsyncThunk, createEntityAdapter } from '#reduxjs/toolkit';
import axios from 'axios';
export const getOrders = createAsyncThunk('eCommerceApp/orders/getOrders', async () => {
const response = await axios.get('/api/e-commerce-app/orders');
const data = await response.data;
return data;
});
export const removeOrders = createAsyncThunk(
'eCommerceApp/orders/removeOrders',
async (orderIds, { dispatch, getState }) => {
await axios.post('/api/e-commerce-app/remove-orders', { orderIds });
return orderIds;
}
);
const ordersAdapter = createEntityAdapter({});
export const { selectAll: selectOrders, selectById: selectOrderById } = ordersAdapter.getSelectors(
state => state.eCommerceApp.orders
);
const ordersSlice = createSlice({
name: 'eCommerceApp/orders',
initialState: ordersAdapter.getInitialState({
searchText: ''
}),
reducers: {
setOrdersSearchText: {
reducer: (state, action) => {
state.searchText = action.payload;
},
prepare: event => ({ payload: event.target.value || '' })
}
},
extraReducers: {
[getOrders.fulfilled]: ordersAdapter.setAll,
[removeOrders.fulfilled]: (state, action) => ordersAdapter.removeMany(state, action.payload)
}
});
export const { setOrdersSearchText } = ordersSlice.actions;
export default ordersSlice.reducer;
In Addition
Also can you please tell me what I will do with this following code for my custom function getOrdersByCustomer.
export const { selectAll: selectOrders, selectById: selectOrderById } = ordersAdapter.getSelectors(
state => state.eCommerceApp.orders
);
because, in my component I have used like
const orders = useSelector(selectOrders);
You can introduce new (async) functions as you already have (I used the customerId as part of the url -> you could access it through the params in your backend):
export const getOrdersByCustomer = createAsyncThunk('eCommerceApp/orders/getOrdersByCustomer', async (customerId) => {
const response = await axios.get(`/api/e-commerce-app/orders/${customerId}`);
const data = await response.data;
return data;
});
Then you can handle the response in your extraReducer:
extraReducers: {
[getOrders.fulfilled]: ordersAdapter.setAll,
[removeOrders.fulfilled]: (state, action) => ordersAdapter.removeMany(state, action.payload),
[getOrdersByCustomer.fulfilled]: (state, action) =>
// set your state to action.payload
}
The extraReducers handle actions like async thunks. The createAsyncThunk function return 3 possible states (along with other things): pending, rejected or fulfilled. In your case you only handle the fulfilled response. You could also set your state with the other two options (in your case [getOrdersByCustomer.pending] or [getOrdersByCustomer.rejected]
The problem is:
I'm trying to use redux-saga in my react app, but i still has this error: Actions must be plain objects. Use custom middleware for async actions. Code it seems correct but no idea why gives that error. I'll be glad for all the help. I'm fighting with it for about two days and still doesn't have a solution. I tried to look up, but I still have this error.
action...
import { GET_DISTRICTS} from '../../constants';
const getAdres = async (url) => {
let response = await fetch(url);
let data = await response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
return list;
};
export const actions = {
handleGetDistrictsData: async () => {
let districts = await getAdres(`url is here`);
return {
type: GET_DISTRICTS,
payload: districts
};
},
reducer...
import { GET_DISTRICTS } from '../../constants';
export const initialState = {
districts: [],
quarters: [],
streets: [],
doors: [],
districtSelected: false,
districtSelectedID: null,
quarterSelected: false,
quarterSelectedID: null,
streetSelected: false,
streetSelectedID: null,
doorSelected: false,
doorSelectedID: null
};
export default (state = initialState, action) => {
switch (action.type) {
case GET_DISTRICTS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
component...
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { actions as addressActions } from '../../../../redux/actions/address';
import Select from 'react-select';
const Districts = (props) => {
let [ fetchedData, setFetchedData ] = useState(false);
useEffect(() => {
props.handleGetDistrictsData();
setFetchedData(true);
});
return (
<React.Fragment>
<Select
name='adresSelect'
options={props.address.districts}
onChange={props.handleDistrictChange}
placeholder='Please Select'
/>
</React.Fragment>
);
};
const mapStateToProps = (state) => ({
address: state.address
});
const mapDispatchToProps = function(dispatch) {
return bindActionCreators({ ...addressActions }, dispatch);
};
export default connect(mapStateToProps, mapDispatchToProps)(Districts);
-------------
import React from 'react';
import Districts from './Districts';
const AddressSearchWidget = (props) => {
return (
<React.Fragment>
<Districts />
</React.Fragment>
);
};
export default AddressSearchWidget
store...
import { applyMiddleware, combineReducers, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootSaga from './sagas/index';
import * as reducers from './';
export function initStore() {
const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const rootReducer = combineReducers(reducers);
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, composeEnhancer(applyMiddleware(sagaMiddleware)));
// Run sagas
sagaMiddleware.run(rootSaga);
return store;
}
handleGetDistrictsData returns a promise (all async functions return promises). You cannot dispatch a promise in plain redux saga, and redux-saga does not change this. Instead, dispatch a normal action, and have that action run a saga. The saga can then do async things, and when it's done dispatch another action. The reducer listens only for that second action.
// Actions:
export const getDistrictsData = () => ({
type: GET_DISTRICTS,
})
export const districtsDataSuccess = (districts) => ({
type: DISTRICTS_DATA_SUCCESS,
payload: districts
})
// Sagas:
export function* watchGetDistricts () {
takeEvery(GET_DISTRICTS, getDistricts);
}
function* getDistricts() {
let response = yield fetch(url);
let data = yield response.json();
let list = [];
data.AdresList.Adresler.Adres.forEach((item) => {
console.info(item);
list.push({
label: item.ADI,
value: item.ID
});
});
yield put(districtsDataSuccess(list));
}
// reducer:
export default (state = initialState, action) => {
switch (action.type) {
case DISTRICTS_DATA_SUCCESS:
return {
...state,
districts: action.payload
};
default:
return state;
}
};
I need to fetch data from server with the help of such instrument as Redux. I watched some tutorials about it and wrote some code for it. Here it is:
actions/fetching_actions.js
import * as Actions from '../constants/action_types';
function fetchListOfCities() {
return fetch(`${Actions.BASE_URL}/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d`);
}
export const listOfCitiesRequest = () => function (dispatch) {
return fetchListOfCities()
.then(list => list.json())
.then((list) => {
dispatch(getListOfCities(list));
}).catch((error) => {
console.log(error);
});
};
export const getListOfCities = result => ({
type: Actions.LIST_RESPONSE,
result,
});
constants/action_types.js
export const BASE_URL = 'http://api.openweathermap.org';
export const LIST_RESPONSE = 'LIST_RESPONSE';
export const CITY_RESPONSE = 'CITY_RESPONSE';
reducers/fetching_reducer.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
reducers/index.js
import * as Actions from '../constants/action_types';
const initialState = {
list: [],
city: {},
};
const FETCHING_REDUCER = (state = initialState, action) => {
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.result,
};
case Actions.CITY_RESPONSE:
return {
...state,
city: action.result,
};
default:
return state;
}
};
export default FETCHING_REDUCER;
And unfortunately I don't know what should I do further. Before I fetched data in this way in Component:
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
this.setState({ data });
}
};
But I heard that it's better to fetch data by redux, so, please, can you explain me how to finish this fetching part, what should I add here?
In saga please import these things
import { put } from 'redux-saga/effects';
getCitiesListFromApiAsync = async () => {
const fetchData = await fetch('http://api.openweathermap.org/data/2.5/find?lat=55.5&lon=37.5&cnt=10&appid=8df903ce56f6d18245e72f380beb297d').then();
const data = await fetchData.json();
if (data.cod !== '200') {
Alert.alert('Loading failed');
} else {
yield put({ type: LIST_RESPONSE, payload: data });
}
};
In reducer
switch (action.type) {
case Actions.LIST_RESPONSE:
return {
...state,
list: action.payload,
};
To send some request to the server via redux you should use one of middlewares:
redux-thunk
redux-promise
redux-saga
redux-observable
etc.
The easiest I think is redux-thunk.
1. Install the package:
$ npm install redux-thunk
2. Connect it to your store:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk)
);
After that, you will be allowed to dispatch to redux not only a plain javascript object but also functions.
3. So you can dispatch like this:
store.dispatch(listOfCitiesRequest());