Load a Spinner in React Redux (Thunk) - reactjs

I have a redux store as follows:
const initState = {
data: {},
isFetching: false,
};
I have a async function to fetch the data for the state:
const requestSideBarData = async(state, actions) => {
const request = actions.request
let newState = state;
const promises = [];
Object.keys(newState.data).forEach(element => {
promises.push(axios.post(`${ApiEndPoints.getDropDownHeaders}${element}`,request))
});
const results = await Promise.all(promises);
var index = 0;
Object.keys(newState.data).forEach(element => {
newState.data[element] = results[index++].data;
})
return newState;
};
And here are my begin and end reducers:
const fetchSideBarDataBegin = (state) => {
return {
...state,
isFetching: true,
}
};
const fetchSideBarDataEnd = (state) => {
return {
...state,
isFetching: false,
}
};
here's my actions function:
export const fetchSidebBatData = (dateData,selections) => {
return {
type: actions.FETCH_SIDEBAR_DATA_BEGIN, //Here there should be some combine action
dateData: dateData,
selections: selections,
requests: {...dateData,...selections}
}
};
And finally here's my reducer:
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA_REQUEST: return fetchSideBarData(state, action);
case actionTypes.FETCH_SIDEBAR_DATA_BEGIN: return fetchSideBarDataBegin(state);
case actionTypes.FETCH_SIDEBAR_DATA_END: return fetchSideBarDataEnd(state);
default: return state;
}
};
What I intend to do is to combine these three reducers into 1: so basically:
FETCH_SIDEBAR_DATA_BEGIN
FETCH_SIDEBAR_DATA_REQUEST
and finally
FETCH_SIDEBAR_DATA_END
what is the right way to perform this operation?

Related

Redux initial state from API issue

My issue is that I want to fetch all products from the database and set them into the Redux initial state, to do this I did an action SET_PRODUCTS_LIST and in the action. payload I simply passed the products fetched in the component (I am using next js), all works fine but when I try to fire another action like ADD_PRODUCT_TO_CART the products in the initial state are gone which it results impossible to add more than 1 product to the cart.
Inside my component:
function Header({ cartProps, setProducts }) {
useEffect(async () => {
const products = await getProducts();
setProducts(products);
}, []);
}
const mapStateToProps = (state) => {
return {
cartProps: state.cartState,
};
};
export default connect(mapStateToProps, {
setProducts,
})(Header);
the action to set products:
import { SET_PRODUCTS_LIST } from "./types";
export const setProducts = (products) => {
return (dispatch) => {
dispatch({
type: SET_PRODUCTS_LIST,
payload: products,
});
};
};
My cart reducer:
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
maybe I am completely doing wrong the logic about fetching the products in order to have them in the initial state.
const initialState = {
itemNumbers: 0,
cartCost: 0,
products: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case SET_PRODUCTS_LIST: {
return {
...state,
products: action.payload,
};
}
case ADD_PRODUCT_TO_CART: {
//let addQuantity = {
// ...state.products.filter((p) => p.productName === action.paylaod),
// };
console.log(state.products);
return {
...state
itemNumbers: state.itemNumbers + 1,
};
}
default:
return state;
}
};
export default reducer;
You should always return state but in ADD_PRODUCT_TO_CART case you return only
{
itemNumbers: state.itemNumbers + 1,
}
so you need to add ...state before itemNumbers: state.itemNumbers + 1,

How to dispatch multiple actions in react redux?

Here is a simple flow of what I want to achieve:
And here is the components executions:
onClick function:
this.props.FetchQueryData({dateRange: this.props.dateData.dateRange, selections: selections});
Redux FetchQueryData function:
export const fetchSidebBatData = (sideBarData) => {
return dispatch => {
dispatch(fetchSidebBatDataBegin()); //For turning on isLoading
console.log("Started");
//For fetching the data
Object.keys(data).forEach(element => { //Here Data is an object to get the right endpoint
axios.post(`${ApiEndPoints.getDropDownHeadersByAdvertiserId}${element}`, sideBarData)
.then(response => {
data[element] = response.data;
//Once the action is completed turning off isLoading
dispatch(fetchSidebBatDataEnd());
})
.catch(e => {
console.log(e);
//If there's an error turning off isLoading
dispatch(fetchSidebBatDataEnd());
});
},console.log(data));
console.log("Ended");
};
};
Here is my reducer:
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA: return fetchSideBarData(state, action);
case actionTypes.FETCH_SIDEBAR_DATA_START: return fetchSideBarDataBegin(state);
case actionTypes.FETCH_SIDEBAR_DATA_END: return fetchSideBarDataEnd(state);
default: return state;
}
};
and here is my fetchSideBarDataBegin and fetchSideBarDataEnd functions:
const fetchSideBarDataBegin = (state) => {
const newState = state;
newState.isFetching = true;
return newState;
};
const fetchSideBarDataEnd = (state) => {
const newState = state;
newState.isFetching = false;
return newState;
};
I know I am missing something critical as I am not at all dispatching the data but I am totally new to redux and don't have a good context of how do you dispatch multiple actions within a reducer. Your help is highly appreciated. If you feel this is kind of complex example you can pick your own and just demonstrate the flow as I have shared in image which would be extremely helpful for me in understanding what to do next.
Note my major problem is that I want to set isLoading to true before fetching the data and that change should reflect in component and once the process is over then I want to turn it back to false.
Thanks in Advance
You can update the reducer with the terminology that is quite common in redux:
const initState = {
data: [],
error: false,
isLoading: false
};
const reducer = ( state = initState, action ) => {
switch ( action.type ) {
case actionTypes.FETCH_SIDEBAR_DATA_REQUEST: {
return {
...state,
data: [], // reset data if previous
error: false, // clear previous error if any
isLoading: true,
}
};
case actionTypes.FETCH_SIDEBAR_DATA_SUCCESS: {
return {
...state,
data: action.data,
isLoading: false,
}
};
case actionTypes.FETCH_SIDEBAR_DATA_ERROR: {
return {
...state,
error: true,
isLoading: false,
}
};
default: return state;
}
};
Notice to have three different action types: FETCH_SIDEBAR_DATA_REQUEST, FETCH_SIDEBAR_DATA_SUCCESS, and FETCH_SIDEBAR_DATA_ERROR
Edit: It seems you are doing multiple request in the thunk, you can handle that as:
export const fetchSidebBatData = (sideBarData) => {
return dispatch => {
dispatch(fetchSidebBatDataRequest());
const requests = Object.keys(data).map(element => {
return axios.post(`${ApiEndPoints.getDropDownHeadersByAdvertiserId}${element}`, sideBarData)
.then(response => {
return { [element]: response.data };
});
};
Promise.all(requests).then(data => {
dispatch(fetchSidebBatDataSuccess(data));
}).catch(error) {
dispatch(fetchSidebBatDataError(error));
}
};
};

Adding existing item to Cart (React-Redux)

I'm using React and Redux for creating shop. I need to add existing item to cart with increasing quantity.
I know, that Redux is based on immutability, but I can't find how to get rid of mutation. Please, give me a hint.
So, there is my code
Actions:
export const UPDATE_QTY = 'UPDATE_QTY';
export const UPDATE_CART = 'UPDATE_CART';
Reducer and initialState:
const initialState = {
cart: [],
qty: 0,
total: 0,
delivery: 5,
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case actions.UPDATE_QTY:
let existedItem = state.cart.filter((cartItem) => cartItem.id === action.payload.id);
existedItem[0].qty = action.payload.qty;
return {
...state,
qty // how to get rid of mutation here?
};
case actions.UPDATE_CART:
return { ...state, cart:[...state.cart, action.payload] };
default:
return state;
}
};
And my Component with dispatch:
export default function AddBtn({ id }) {
const itemData = useSelector((state) => state.main.items);
const cartData = useSelector((state) => state.app.cart);
const dispatch = useDispatch();
const handleAddToCart = () => {
const addedItem = itemData.find((item) => item.id === id);
const existedItem = cartData.find((item) => id === item.id);
if (existedItem) {
dispatch({
type: UPDATE_QTY,
payload: { id, qty: existedItem.qty + 1 },
});
} else {
dispatch({
type: UPDATE_CART,
payload: addedItem,
});
}
return (
// JSX code
)
You can use map function instead, which are immutable:
const initialState = {
cart: [],
qty: 0,
total: 0,
delivery: 5,
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case actions.UPDATE_QTY:
return {
...state,
cart: state.cart.map(el => {
if (el.id === action.payload.id) {
return {
...el,
qty: action.payload.qty
}
}
return el;
})
};
case actions.UPDATE_CART:
return { ...state,
cart: [...state.cart, action.payload]
};
default:
return state;
}
};
You can try this:
const initialState = {
cart: [],
qty: 0,
total: 0,
delivery: 5,
};
export const cartReducer = (state = initialState, action) => {
switch (action.type) {
case actions.UPDATE_QTY:
let existedItem = state.cart.find((cartItem) => cartItem.id === action.payload.id);
if(existedItem){
existedItem.qty = action.payload.qty;
}
return {
...state,
cart:[...state.cart] // how to get rid of mutation here?
};
case actions.UPDATE_CART:
return { ...state, cart:[...state.cart, action.payload] };
default:
return state;
}
};

Respond to a Single Redux Action in Multiple Reducers redux

I am using multiple reducers in my project and then combining them with combineReducers() function and have all actions in single file. when i dispatch the action, it is returning me state values to undefined. I think It can't find out because of multiple reducerse. But when i use single reducer file. It is working fine. Can anyone please tell me what the issue.It is how i am combining the reducers.
const rootReducer = combineReducers({
isMobileReducer,
imageSliderReducer
})
and now passing to store, like below:
let store = createStore(rootReducer,applyMiddleware(thunk))
and in frontend how i am accessing state
const mapStateToProps = (state) => ({
images: state.images,
isMobile: state && state.isMobile
})
imageSliderReducer.js
import {
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from '../actions/actionTypes'
const initialState = {
images:[],
error:null
}
const imageSliderReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_BEGIN:
return {...state,error:null}
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
case FETCH_IMAGES_FAILURE:
return {...state,error:action.payload.error,images:[]}
default:
return state
}
}
export default imageSliderReducer;
isMobileReducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false
}
const isMobileReducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
default:
return state
}
}
export default isMobileReducer;
actionCreator.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from './actionTypes'
export function openMenu(isMobile) {
return {
type: OPEN_MENU
}
}
export function closeMenu(isMobile) {
return {
type: CLOSE_MENU
}
}
export function setDeviceType (isMobile) {
return {
type: SET_DEVICE_TYPE,
isMobile: isMobile
}
}
export function fetchImages() {
return dispatch => {
dispatch(fetchImagesBegin());
return fetch("https://7344.rio.com/wp-json/customapi/homeslider")
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
.catch(error => dispatch(fetchImagesFailure(error)));
};
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchImagesBegin = () => ({
type: FETCH_IMAGES_BEGIN
});
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
export const fetchImagesFailure = error => ({
type: FETCH_IMAGES_FAILURE,
payload: { error }
});
Try using this:
const mapStateToProps = (state) => ({
images: state.imageSliderReducer.images,
isMobile: state.isMobileReducer.isMobile
})

ReactJS modifiying data in state

I just starting on React, and starting to do a todo list. It has functionalities like add, modify(done/pending) and remove task.
Below is my action
export const ADD_TASK = 'ADD_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
export const FILTER_TASK = 'FILTER_TASK';
let todoId = 1;
export function addTask(task) {
let todo = {
id: todoId++,
name: task,
status: 0,
visible: true
};
return {
type: ADD_TASK,
payload: todo
};
}
export function toggleTask(id) {
return {
type: TOGGLE_TASK,
payload: id
};
}
export function removeTask(id) {
return {
type: REMOVE_TASK,
payload: id
};
}
export function filterTask(id) {
return {
type: FILTER_TASK,
payload: id
};
}
and my reducer :
import { ADD_TASK, TOGGLE_TASK, REMOVE_TASK, FILTER_TASK } from '../actions/index';
let filterStatus = -1;
//initial state is array because we want list of city weather data
export default function(state = [], action) {
// console.log('Action received', action);
const toggling = function (t, action) {
if(t.id !== action)
return t;
return Object.assign({}, t, {
status: !t.status
})
};
const visibility = function(t, action) {
return Object.assign({}, t, {
visible: action === -1 ? true : t.status == action
})
};
switch(action.type) {
case ADD_TASK :
//return state.concat([ action.payload.data ]); //in redux reducer dont modify the state, instead create a new one baesd on old one. Here concat is create a new of old one and add a new data
return [ action.payload, ...state];
case TOGGLE_TASK :
return state.map(s => toggling(s, action.payload)).map(t => visibility(t, filterStatus));
case REMOVE_TASK :
return state.filter(s => { return (s.id != action.payload) } );
case FILTER_TASK :
filterStatus = action.payload;
return state.map(t => visibility(t, action.payload));
}
return state;
}
I read somewhere that modifying state is reducer is a bad practice, yet I feel that I'm doing it in my reducer.
Could anyone suggest the correct way of handling add,remove, update value state in the reducer ?
Thank you
i think you need two reducers: one for managing visibility stuff, one for adding, toggling and removing tasks.
so for the second part i would like do this.
export const ADD_TASK = 'ADD_TASK';
export const TOGGLE_TASK = 'TOGGLE_TASK';
export const REMOVE_TASK = 'REMOVE_TASK';
let todoId = 1;
export addTask = (text) => ({
type: ADD_TASK,
id: todoId++,
text
});
export toggleTask = (id) => ({
type: TOGGLE_TASK,
id
});
export removeTask = (id) => ({
type: REMOVE_TASK,
id
});
export function todosReducer(state = [], action) {
switch(action.type) {
case ADD_TASK :
return [...state, {id: action.id, text: action.text, completed: false}];
case TOGGLE_TASK :
return state.map(task => task.id !== action.id ? task : {...task, completed: !task.completed});
case REMOVE_TASK :
return state.filter(task => task.id !== action.id);
}
return state;
}

Resources