how to restore reducer state value to the initialState default value - reactjs

I'm trying to filter items by their category using useReducer
context.jsx
const initialState = {
categoryName: "all item",
};
const [state, dispatch] = useReducer(reducer, initialState);
const fetchUrl = async () => {
const resp = await fetch(url);
const respData = await resp.json();
const item = respData.item;
const category = respData.category;
const promo = respData.promo;
dispatch({ type: "CATEGORY_ITEM", payload: category });
};
I want to display the category name that matched the data.
reducer.jsx
if (action.type === "FILTER_NAME") {
if (action.payload === "all menu") {
return { ...state, categoryName: "all menu" };
//return { ...state, categoryName: state.categoryName};
} else {
return { ...state, categoryName: action.payload };
}
}
I cant set the categoryName back to the state value because it's been changed when I do else.
Is there a way for me to set a default value in reducer? Because if I use useState the setState won't overwrite the state default value.
Thanks before

Related

How to load data from Firestore in ReactJS shopping cart instead of LocalStroage

My cart context is below where I am setting the data in localstorage if cart content changes and displaying that data using getLocalData() function on cart load
const getLocalData = () => {
const item = localStorage.getItem("cartItem");
const parsedData = JSON.parse(item);
if (!Array.isArray(parsedData)) return [];
return parsedData;
};
const initialState = {
cart: getLocalData(),
cartLoading: true,
cartError: null,
totalPrice: 0,
totalAmount: 0,
deliveryFee: 6.12,
};
const CartProvider = ({ children }) => {
const { user, cart } = useAuthContext();
const [state, dispatch] = useReducer(reducer, initialState);
const [fetchCart, setFetchCart] = useState([]);
const addToCart = (product) => {
dispatch({ type: "ADD_TO_CART", payload: product });
};
//using this function to add cart data in firestore collections
const getCartData = async () => {
try {
const docRef = await addDoc(collection(db, "cart"), {
cart: state.cart,
});
console.log("Document written with ID: ", docRef.id);
} catch (e) {
console.error("Error adding document: ", e);
}
};
//using this function to fetch the data from firestore and set it to cart
const datas = async (users) => {
const docRef = collection(db, "cart");
const snapshot = await getDocs(docRef);
const data = snapshot.docs.map((x) => x.data().cart);
//console.log(data);
//console.log(data[data.length - 1]);
setFetchCart(data[data.length - 1]);
};
useEffect(() => {
dispatch({ type: "TOTAL_CART_PRICE_AMOUNT" });
//localStorage.setItem("cartItem", JSON.stringify(state.cart));
datas().then(() => dispatch({ type: "ADD_FROM_FIREBASE", payload: fetchCart }));
getCartData();
}, [state.cart]);
return (
<CartContext.Provider value={{ ...state, addToCart, removeCart, setDecrease, setIncrease }}>
{children}
</CartContext.Provider>
);
};
My cart reducer
const CartReducer = (state, { type, payload }) => {
switch (type) {
case "ADD_FROM_FIREBASE":
console.log(payload);
return {
...state,
cart: payload,
};
case "ADD_TO_CART":
const { id, title, price, stock, description, images, discountPercentage, brand } = payload;
let existingProduct = state.cart.find((cart) => cart.id === id);
if (existingProduct) {
let updatedProduct = state.cart.map((currElem) => {
if (currElem.id === id) {
let newAmount = currElem.amount + 1;
if (currElem.amount >= stock) {
newAmount = currElem.stock;
}
return {
...currElem,
amount: newAmount,
};
} else {
return {
...currElem,
};
}
});
return {
...state,
cart: updatedProduct,
};
} else {
let amount = 1;
let cartProduct = {
title,
price,
stock,
description,
images,
id,
discountPercentage,
brand,
amount,
};
return {
cart: [...state.cart, cartProduct],
};
}
case "REMOVE_CART":
return {
...state,
cart: state.cart.filter((cart) => cart.id != payload),
};
case "TOTAL_CART_PRICE_AMOUNT":
const { totalPrice, totalAmount } = state.cart.reduce(
(acc, curr) => {
let { price, amount } = curr;
acc.totalAmount += amount;
acc.totalPrice += price * amount;
return acc;
},
{ totalPrice: 0, totalAmount: 0 }
);
return {
...state,
totalAmount,
totalPrice,
};
default:
return {
...state,
};
}
};
I tried to add cartData in firestore using getCartData() function which is working correctly. But when I am trying to read data from firestore and set its value to cart, it is not working.
I wants functionality where, logged in user when adds something to cart, its data should get updated in firestore collections db and then fetch that data from firestore and display data in cart. It should be different for different users.
Till now I have implemented the functionality of adding cart data in localStorage and displayin that data from localStorage. But using localStorage,the same cart data is displayed to different logged in users.
when I uses datas().then(()=> ...) in useEffect it reads data but since it is fecthing data and updating fetchCart, it goes into infinite rendering loop.
So any help would be appreciated in this matter

React Native Asyncstorage / useReducer returns null value

Anybody has experience in AsyncStorage in React Native? It returns wired values something like this.
"_U": 0,
"_V": 1,
"_X": null,
"_W": {}
And here is Context, useReducer hook code.
const [localState, localDispatch] = useReducer(
local,
localInitialState,
async () => {
await AsyncStorage.removeItem(‘local’);
const storedLocalData = await AsyncStorage.getItem(‘local’);
console.log(‘LOCAL: ’, storedLocalData);
storedLocalData ? console.log(‘LOCAL-YES’) : console.log(‘LOCAL-NO’);
return storedLocalData ? JSON.parse(storedLocalData) : localInitialState;
},
);
const [themeState, themeDispatch] = useReducer(
themeReducer,
themeInitialState,
async () => {
await AsyncStorage.removeItem(‘theme’);
const storedThemeData = await AsyncStorage.getItem(‘theme’);
console.log(‘THEME: ’, storedThemeData);
storedThemeData ? console.log(‘THEME-YES’) : console.log(‘THEME-NO’);
return storedThemeData ? JSON.parse(storedThemeData) : themeInitialState;
},
);
Local state works well but theme sate which copied from local does not work...
And this is Console state.
Local state already stored in Asyncstorage. but Theme state returns null.. 😦
with the same code..
the State should be works like local state. not the theme state.
I hope any advise, Thanks.
Unfortunately there's no possibility for useReducer to have a function that returns a Promise as initializer for now! (which I think it's necessary for the next updates of React)
but here's my solution for now: (written in typescript)
import React from "react";
import { CommonActionTypes } from "context/common/CommonActions";
import useStorage from "./useStorage";
/**
* --- IMPORTANT ----
* if you're using this wrapper, your reducer must handle the ReplaceStateAction
* **Also** your state needs to have a property named `isPersistedDataReady` with `false` as default value
*/
export function usePersistedReducer<State, Action>(
reducer: (state: State, action: Action) => State,
initialState: State,
storageKey: string,
): [State, React.Dispatch<Action>] {
const { value, setValue, isReady } = useStorage<State>(storageKey, initialState);
const reducerLocalStorage = React.useCallback(
(state: State, action: Action): State => {
const newState = reducer(state, action);
setValue(newState);
return newState;
},
[value],
);
const [store, dispatch] = React.useReducer(reducerLocalStorage, value);
React.useEffect(() => {
isReady &&
// #ts-ignore here we need an extension of union type for Action
dispatch({
type: CommonActionTypes.ReplaceState,
state: { ...value, isPersistedDataReady: true },
});
}, [isReady]);
return [store, dispatch];
}
then in your views isPersistedDataReady value.
here's also the implementation of the hook useStorage
import AsyncStorage from "#react-native-async-storage/async-storage";
const useStorage = <T>(key: string, defaultValue: T) => {
type State = { value: T; isReady: boolean };
const [state, setState] = React.useState<State>({
value: defaultValue,
isReady: false,
});
React.useEffect(() => {
get()
.then((value) => {
setState({ value, isReady: true });
})
.catch(() => {
setState({ value: defaultValue, isReady: true });
});
}, []);
React.useEffect(() => {
state.value && state.isReady && save(state.value);
}, [state.value]);
const setValue = (value: T) => {
setState({ value, isReady: true });
};
const save = (value: T): Promise<void> => {
if (value) {
try {
const savingValue = JSON.stringify(value);
return AsyncStorage.setItem(key, savingValue);
} catch (er) {
return Promise.reject(er);
}
} else {
return Promise.reject(Error("No value provided"));
}
};
const get = (): Promise<T> => {
return AsyncStorage.getItem(key, () => defaultValue).then((value) => {
if (value === null) {
throw Error(`no value exsits for ${key} key in the storage`);
}
return JSON.parse(value);
});
};
const remove = (): Promise<void> => {
return AsyncStorage.removeItem(key);
};
return { ...state, setValue, clear: remove };
};
export default useStorage;

axios delete operation with use reducer in react js

i am working on crud operation with context api and use reducer. i fetch data from an api and store it as initial value. but now i am confused how to delete a user from my fetched list. i made a remove function which works fine on manual refresh but does not auto refresh and gives back an error. how to make a delete function.
const InitialState = {
Users: []
}
const Reducer = (state, action) =>
{
switch(action.type)
{
case 'FETCH_USERS':
return{
Users: action.payload
}
case 'REMOVE_USER':
return{
Users: action.payload
}
default:
return state
}
}
const GlobalContext = createContext(InitialState)
const GlobalState = ({children}) => {
const [state, dispatch] = useReducer(Reducer, InitialState);
useEffect(()=>
{
fecthapi();
},[])
const fecthapi = async () =>
{
const res = await axios.get('http://localhost:3002/users')
dispatch({type: 'FETCH_USERS', payload: res.data})
}
const Remove = async (id) =>
{
await axios.delete(`http://localhost:3002/users/${id}`)
dispatch({
type: 'REMOVE_USER', payload: id
})
}
return (
<>
<GlobalContext.Provider value = {{Users: state.Users, Remove: Remove}}>
{children}
</GlobalContext.Provider>
</>
)
}```

Reaching Nested State data w/ Reducer using immer

I got data.js that contains name, listOfReview that have sub nested state(name, occupation and etc). i'm using useReducer to add another review on the listofreview. but the problem is after posting a review it will create a new object outside the listofreview. The output
data.js
export const data = [
{
id: 1607089645363,
name: 'john',
noOfReview: 1,
listOfReview: [
{
reviewId: 1607089645361,
name: 'john doe',
occupation: 'hero',
rating: 5,
review: 'lorem ipsum',
}
]
},
];
index.js
import { data } from '../../src/data';
import { reducer } from './reducer';
const defaultState = {
review: data
}
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, defaultState);
const handelSubmit = (e) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { Reviewid: new Date().getTime().toString(), rating, name, occupation, reviews };
dispatch({ type: 'ADD_REVIEW_ITEM', payload: newReview });
}
}
}
reducer.js
export const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
return state.map((data) => {
if (data) {
const newReview = [...data.listOfReview, action.payload];
return {
...data,
listOfReview: newReview
};
}
return data;
});
default:
return state;
}
};
OUTPUT
According to your data structure, you have an array of listOfReview in an array of data:-
These 2 fixes may help you:-
FIXES 1: You need to pass the selected id of data you want to add new review to
FIXES 2: You need to change the way you code your reducer so that it will update the state correctly and not mutating the current state
Fixes 1
index.js. Adjust your handleSubmit to receive arg of selected id of data you want to add *new review obj. Thus, send both 'selectedDataId' and newReview via dispatch payload
import { data } from '../../src/data';
import { reducer } from './reducer';
const ModalHandler = props => {
const [rating, setRating] = useState(null);
const [name, setName] = useState('');
const [occupation, setOccupation] = useState('');
const [reviews, setReviews] = useState('');
const [state, dispatch] = useReducer(reducer, defaultState);
const handelSubmit = (e, selectedDataId) => {
e.preventDefault();
if (name && occupation && reviews) {
const newReview = { Reviewid: new Date().getTime().toString(), rating, name, occupation, reviews };
dispatch({
type: 'ADD_REVIEW_ITEM',
payload: {
selectedDataId: selectedDataId,
newReview: newReview
}
});
}
}
}
Fixes 2
in reducer. Adjust so that it will not mutate your current state
const reducer = (state, action) => {
switch (action.type) {
case "ADD_REVIEW_ITEM":
return {
...state,
review: state.review.map((data) => {
if (data.id === action.payload.selectedDataId) {
return {
...data,
listOfReview: [...data.listOfReview, action.payload.newReview]
};
}
return data;
})
};
default:
return state;
}
};
This a working sandbox of your case.

Combining global redux store with local state of the component

The challenge I came across is using global store slice, namely 'genres', which is an array of objects, in a local state to manipulate check/uncheck of the checkboxes. The problem occurs when I'm trying to use props.genres in the initial state. Looks like I'm getting an empty array from props.genres when the local state is initialized.
const Filters = (props) => {
const { genres, getSelected, loadGenres, getGenres, clearFilters } = props
const [isChecked, setIsChecked] = useState(() =>
genres.map(genre => (
{id: genre.id, value: genre.name, checked: false}
))
)
const optionsSortBy = [
{name: 'Popularity descending', value: 'popularity.desc'},
{name: 'Popularity ascending', value: 'popularity.asc'},
{name: 'Rating descending', value: 'vote_average.desc'},
{name: 'Rating ascending', value: 'vote_average.asc'},
]
const d = new Date()
let currentYear = d.getFullYear()
let optionsReleaseDate = R.range(1990, currentYear + 1).map(year => (
{name: year + '', value: year}
))
useEffect(() => {
const url = `${C.API_ENDPOINT}genre/movie/list`
loadGenres(url, C.OPTIONS)
}, [])
const handleCheckbox = (e) => {
let target = e.target
getGenres(target)
}
const handleSelect = (e) => {
let target = e.target
let action = isNaN(target.value) ? 'SORT_BY' : 'RELEASE_DATE'
getSelected(action, target)
}
const handleSubmitBtn = (e) => {
e.preventDefault()
clearFilters()
}
return (
<form className={classes.FiltersBox}>
<Submit submited={handleSubmitBtn} />
<Select name="Sort By:" options={optionsSortBy} changed={handleSelect} />
<Select name="Release date:" options={optionsReleaseDate} changed={handleSelect} />
<Genres genres={isChecked} changed={handleCheckbox} />
</form>
)
}
const mapStateToProps = (state) => {
return {
genres: state.fetch.genres,
}
}
const mapDispatchToProps = (dispatch) => {
return {
loadGenres: (url, options) => dispatch(A.getApiData(url, options)),
getGenres: (targetItem) => dispatch({
type: 'CHECK_GENRES',
payload: targetItem
}),
getSelected: (actionType, targetItem) => dispatch({
type: actionType,
payload: targetItem,
}),
clearFilters: () => dispatch({type: 'CLEAR_FILTERS'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Filters);
import * as R from 'ramda';
import fetchJSON from '../utils/api.js';
export const getApiData = (url, options) => async (dispatch) => {
const response = await fetchJSON(url, options)
const data = response.body
const dataHas = R.has(R.__, data)
let actionType = dataHas('genres') ? 'FETCH_GENRES' : 'FETCH_MOVIES'
dispatch({
type: actionType,
payload: data
})
}
export const fetchReducer = (state = initialState, action) => {
const { payload } = action
if (action.type === 'FETCH_GENRES') {
return {
...state,
isLoading: false,
genres: [...payload.genres]
}
}
if (action.type === 'FETCH_MOVIES') {
return {
...state,
isLoading: false,
movies: [...payload.results]
}
}
return state
}
What you are trying to do of setting initial value for state from props, is possible but isn't react best practice. Consider initial your data as empty array and through useEffect manipulate state
// didn't understand if its array or bool
const [isChecked, setIsChecked] = useState([])
useEffect(()=>genres&& { setIsChecked(... perform action...)
} ,[genres])
You approach is almost correct.
I am not sure how the state should look like, when you have fetched your data.
I can see in the mapStateToProps is trying to access a value which is not defined at the beginning. If state.fetch is undefined you can not access genres.
Attempt 1:
You can solve it by using lodash.get https://lodash.com/docs/#get
It will catch up for the undefined problem.
Attempt 2:
You can defined an initial state where your values are defined with mock data.
const initialState = {fetch: {genres: []}}
and use it your reducer

Resources