I want to save my data in localstorage to evade the loss of it when reloading the page but i also need it in my gloable state to show a preview of it once it's added and never be lost when reloading the page,This is my slice format:
import { createSlice } from "#reduxjs/toolkit";
export const resumeSlicer = createSlice({
name: "resume",
initialState: {
Education: [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
],
},
reducers: {
SaveEducation: (state, action) => {
let Education = JSON.parse(localStorage.getItem("Education"));
if (!Education) {
Education.push(action.payload);
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
} else {
Education.push(action.payload);
let i = 0;
Education.map((e) => {
e.key = i;
i++;
return e.key;
});
localStorage.setItem("Education", JSON.stringify(Education));
state.Education = Education;
}
},
getEducation: (state, action) => {
const items = JSON.parse(localStorage.getItem("Education"));
const empty_array = [
{
key: NaN,
Title: "",
Date: "",
Establishment: "",
Place: "",
},
];
state.Education.splice(0, state.Education.length);
state.Education = items;
},
},
});
And this is how i fetched:
const EdList = useSelector((state) => state.Education);
When i console.log it the result is "undefined"
Image Preview
https://i.stack.imgur.com/hD8bx.png
I'm hazarding a guess that the issue is a missing reference into the state. The chunk of state will typically nest under the name you give the slice, "resume" in this case. This occurs when you combine the slice reducers when creating the state object for the store.
Try:
const EdList = useSelector((state) => state.resume.Education);
If it turns out this isn't the case then we'll need to see how you create/configure the store and how you combine your reducers.
Related
I tried changing a key's value inside my object, but it seems like I have been mutating the state.
The issue happens in state dispatch
Code:
export function nameList(id, rank, sub) {
const { nameItem: name } = store.getState().nameListSlice; //gets the nameItem state
if (!name.length || name.length === 0) {
const newName = [
{
name: "Male",
url: getNameUrl("Male", id, rank, sub),
icon: "Male",
toolTip: "Male",
active: false,
idName: "male",
},
{
name: "Female",
url: getNameUrl("Female", id, rank, sub),
icon: "Female",
toolTip: "Female",
active: false,
idName: "female",
},
];
store.dispatch(updateDetails(newName)); //The issue happen here
} else {
return name.map((n) => {
n.url = getNameUrl(n.name, id, rank, sub);
return n;
});
}
}
// This function returns a url
function getNameUrl(type, id, rank, sub) {
switch (type) {
case "Male": {
return `/male/${id}/${rank}/${sub}`;
}
case "Female": {
return `/female/${id}/${rank}/${sub}`;
}
}
}
Reducer/Action: (redux toolkit)
export const nameListSlice = createSlice({
name: 'nameItem',
initialState,
reducers: {
updateDetails: (state,action) => {
state.nameItem = action.payload
},
},
})
export const { updateDetails } = nameListSlice.actions
export default nameListSlice.reducer
The error I get:
TypeError: Cannot assign to read only property 'url' of object '#<Object>'
This happens in the above code disptach - store.dispatch(updateDetails(newName));
Commenting this code, fixes the issue.
How to dispatch without this error ?
Also based on this Uncaught TypeError: Cannot assign to read only property
But still same error
RTK use immerjs underly, nameItem state slice is not a mutable draft of immerjs when you get it by using store.getState().nameListSlice. You can use the immutable update function createNextState to perform immutable update patterns.
Why you can mutate the state in case reducers are created by createSlice like state.nameItem = action.payload;?
createSlice use createReducer to create case reducers. See the comments for createReducer:
/**
* A utility function that allows defining a reducer as a mapping from action
* type to *case reducer* functions that handle these action types. The
* reducer's initial state is passed as the first argument.
*
* #remarks
* The body of every case reducer is implicitly wrapped with a call to
* `produce()` from the [immer](https://github.com/mweststrate/immer) library.
* This means that rather than returning a new state object, you can also
* mutate the passed-in state object directly; these mutations will then be
* automatically and efficiently translated into copies, giving you both
* convenience and immutability.
The body of every case reducer is implicitly wrapped with a call to
produce(), that's why you can mutate the state inside case reducer function.
But if you get the state slice outside the case reducer, it's not a mutable draft state, so you can't mutate it directly like n.url = 'xxx'.
E.g.
import { configureStore, createNextState, createSlice, isDraft } from '#reduxjs/toolkit';
const initialState: { nameItem: any[] } = { nameItem: [{ url: '' }] };
export const nameListSlice = createSlice({
name: 'nameItem',
initialState,
reducers: {
updateDetails: (state, action) => {
state.nameItem = action.payload;
},
},
});
const store = configureStore({
reducer: {
nameListSlice: nameListSlice.reducer,
},
});
const { nameItem: name } = store.getState().nameListSlice;
console.log('isDraft: ', isDraft(name));
const nextNames = createNextState(name, (draft) => {
draft.map((n) => {
n.url = `/male`;
return n;
});
});
console.log('nextNames: ', nextNames);
Output:
isDraft: false
nextNames: [ { url: '/male' } ]
I am using Redux-toolkit. I am trying to prevent duplicate items in my store and also want to if any user tries to add the same item again. This time I want to simple update her quantity.
Here is my code:
import { createSlice, current } from "#reduxjs/toolkit";
const initialState = {
product: [],
};
export const cartSlice = createSlice({
name: "cart",
initialState: initialState,
reducers: {
addproduct: (state, action) => {
console.log(action.payload._id) // here _id is ok
const exist = state.product.find((pro) => pro._id === action.payload._id)
console.log(exist) //output undefined
state.product = [...state.product, action.payload];
},
},
})
export const { addproduct } = cartSlice.actions;
export default cartSlice.reducer;
My JSON file:
[
{
"_id": "62ad4c398bc6d37767e44423",
"name": "singu pizza",
"size": "small",
"category": "neapolitan",
"price": 250,
"image": "https://i.ibb.co/zVbq909/pizza.png"
},
{
"_id": "62ad4c398bc6d37767e44424",
"name": "singu pizza",
"size": "large",
"category": "neapolitan",
"price": 250,
"image": "https://i.ibb.co/zVbq909/pizza.png"
}
]
Assuming that there is a quantity property on your product:
addproduct: (state, action) => {
const item = state.product.find((pro) => pro._id === action.payload._id)
if (item) {
item.quantity++
} else {
state.product.push(action.payload)
}
},
If you are using sanity in your project follow the code below, it will update your quantity instead of adding the same product again in the cart, if you are not using sanity have a look at the code below I am sure it will give you some ideas to solve your problem.
Update your cartSlice with the code below :
const initialState = {
product: [],
};
export const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addproduct: (state, { payload }) => {
let newItem = payload;
const existItem = state.products.find((item) => item._id === payload._id);
state.products = existItem ? state.products.map((item) => item._id === existItem._id ? newItem : item) : [...state.products, newItem]
}
},
})
In the the page you want to use the reducer in add this:
const { products } = useSelector(store => store.cart)
const addToCart = () => {
let cartItem = products.find(item => item._id === product._id)
let quantity = cartItem ? cartItem.quantity + 1 : 1;
dispatch(() => addproduct({
_id: product._id,
name: product.name,
size: product.size,
category: product.slug.current,
price: product.price,
image: urlFor(product.image),
quantity
}))
}
addToCart is an onClick function, you do not need to add quantity property to your json file or the api it will be added when the product is selected to be in cart. The quantity above it will check if the item does not exist in the cart the quantity will be 1 and it will be added to the object of the product item, If the item does exist in the cart it will update the quantity by one.
Now everything should work just fine. If you had an error please feel free to message me, I would be more than happy to help you
I'm trying to recreate a Memory-like game with React. I'm using Redux Toolkit for state management, but I'm having trouble with one use case.
In the selectCard action, I want to add the selected card to the store, and check if there's already 2 of them selected. If so, I want to empty the selected array after a delay.
const initialState : MemoryState = {
cards: [],
selected: [],
}
const memorySlice = createSlice({
name: 'memory',
initialState: initialState,
reducers: {
selectCard(state: MemoryState, action: PayloadAction<number>) {
state.selected.push(action.payload);
if (state.selected.length === 2) {
setTimeout(() => {
state.selected = [];
}, 1000);
}
}
}
});
The cards get selected just fine, but when I select 2 I get this error after 1 sec:
TypeError: Cannot perform 'set' on a proxy that has been revoked, on the line state.selected = [];
I'm new to this stuff, how do I access the state after a delay? Do I have to do it asynchronously? If so, how?
As stated in their documentation, don't perform side effects inside a reducer.
I would add the setTimeout when dispatching the action instead:
// so the reducer:
...
if (state.selected.length === 2) {
state.selected = [];
}
...
// and when dispatching
setTimeout(() => {
dispatch(selectCard(1))
}, 1000)
I ran into this issue too. I solved it by using the 'side effect' code into a function and then used the result of it in the reducer
const initialState : MemoryState = {
cards: [],
selected: [],
}
const memorySlice = createSlice({
name: 'memory',
initialState: initialState,
reducers: {
selectCard(state: MemoryState, action: PayloadAction<number>) {
state.selected = action.payload
}
}
});
export const { selectCard } = memorySlice.actions
export const sideEffectFunc = (param) => (dispatch) => {
let selected = []
selected.push(action.payload);
if (selected.length === 2) {
setTimeout(() => {
selected = [];
}, 1000);
}
dispatch(selectCard(selected));
};
Don't pay attention to the logic of the function (haven't tested it and it might be wrong) but I wanted to show the way we could handle 'side effect' code when using redux toolkit
I building small budget calculator and its the first time i am using redux-toolkit, the problem is
How can share/pass state between reducers in redux-toolkit ? (how can use the totalIncomes and totalExpenses in the balance slice to calculate the total balance ?
another question is is ok to use redux-toolkit instead of plain redux
incomes.js :
const incomesSlice = createSlice({
name: "incomes",
initialState: {
list: [],
loading: false,
totalIncomes: 0,
lastFetch: null,
},
reducers: {
ADD_INCOME: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_INCOME: (state, action) => {
const index = state.list.findIndex(
(income) => income.id === action.payload.id
);
state.list.splice(index, 1);
},
TOTAL_INCOMES: (state, action) => {
state.totalIncomes = state.list.reduce(
(acc, curr) => acc + curr.amount,
0
);
},
},
});
expenses.js :
const expensesSlice = createSlice({
name: "expenses",
initialState: {
list: [],
loading: false,
totalExpenses: 0,
lastFetch: null,
},
reducers: {
ADD_EXPENSE: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_EXPENSE: (state, action) => {
const index = state.list.findIndex(
(expense) => expense.id === action.payload.id
);
state.list.splice(index, 1);
},
TOTAL_EXPENSES: (state, action) => {
state.totalExpenses = state.list.reduce(
(acc, curr) => acc + curr.amount,
0
);
},
},
});
export const {
ADD_EXPENSE,
REMOVE_EXPENSE,
TOTAL_EXPENSES,
} = expensesSlice.actions;
export default expensesSlice.reducer;
balance.js :
const balanceSlice = createSlice({
name: "balance",
initialState: {
total: 0
},
reducers: {
CALC_TOTAL: (state, action) => {
// How to Calculate this ?
},
},
});enter code here
export const { CALC_TOTAL } = balanceSlice.actions;
export default balanceSlice.reducer;
For anyone looking into this - author's is the wrong approach to using redux for state management.
When using redux you want your state as normalized as possible - you shouldn't store uneeded/duplicated state or state that can be calculated based on other state, in this example there's no need to save totalIncomes since we can calculate this based on the list of incomes (same goes for totalExpenses and balance).
As mentioned, the totalIncomes shouldn't be part of the state but should be a calculated value, you can either calculate it on the fly or use a selector. In the below example I'll use a selector.
Redux Toolkit solution
To use it with Redux toolkit it might look something like this, I've removed parts of code for brewity:
incomes slice
// ...
const incomesSlice = createSlice({
name: "incomes",
initialState: {
list: [],
},
reducers: {
ADD_INCOME: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_INCOME: (state, action) => {
const index = state.list.findIndex(
(income) => income.id === action.payload.id
);
state.list.splice(index, 1);
},
},
});
export const getTotalIncome = createSelector(
totalIncomeSelector,
calculateTotalIncome,
);
export function totalIncomeSelector(state) {
return state.incomes.list;
}
export function calculateTotalIncome(incomesList) {
return incomesList.reduce((total, income) => total + income.amount);
}
export const {
ADD_INVOICE,
REMOVE_INVOICE,
} = incomesSlice.actions;
export default incomesSlice.reducer;
expenses slice - removed parts for brewity
// ...
const expensesSlice = createSlice({
name: "expenses",
initialState: {
list: [],
},
reducers: {
ADD_EXPENSE: (state, action) => {
state.list.push({
id: uuidv4(),
description: action.payload.description,
amount: action.payload.amount,
});
},
REMOVE_EXPENSE: (state, action) => {
const index = state.list.findIndex(
(income) => income.id === action.payload.id
);
state.list.splice(index, 1);
},
},
});
export const getTotalExpense = createSelector(
totalExpenseSelector,
calculateTotalExpense,
);
export function totalExpenseSelector(state) {
return state.expenses.list;
}
export function calculateTotalExpenseexpenseList) {
return expensesList.reduce((total, expense) => total + expense.amount);
}
export const {
ADD_EXPENSE,
REMOVE_EXPENSE,
} = expensesSlice.actions;
export default expensesSlice.reducer;
balance slice - you don't really need a slice here, you just need a selector
import { getTotalIncome, totalIncomeSelector } from './incomeSlice';
import { getTotalExpense, totalExpenseSelector } from './expenseSlice';
export const getBalance = createSelector(
getTotalIncome,
getTotalExpense,
(totalIncome, totalExpense) => totalIncome - totalIncome,
);
Example component
// ...
function BalanceComponent({
totalIncome,
totalExpense,
balance,
}) {
return (
<div>
<h1>Finance overview</h1>
<div>
<span>Total Income:</span>
<span>{totalIncome}</span>
</div>
<div>
<span>Total Expense:</span>
<span>{totalExpense}</span>
</div>
<div>
<span>Balance:</span>
<span>{balance}</span>
</div>
</div>
);
}
function mapStateToProps(state) {
return {
totalIncome: getTotalIncome(state),
totalExpense: getTotalExpense(state),
balance: getBalance(state),
}
}
export default connect(mapStateToProps)(BalanceComponent);
Note: In the question the author seems to be breaking up his state into too many slices, all this can be a lot simpler by having it all as a single slice. That's what I would do.
Is it ok to use redux-toolkit instead of plain redux
YES. It was originally created to help address common concerns about Redux. See its purpose.
How can share/pass state between reducers in redux-toolkit?
You can pass the used state parts to action.payload.
dispatch(CALC_TOTAL(totalIncomes,totalExpenses))
You can use extraReducers and "listen" to to your incomes/expenses changes.
You can create a middleware or use createAsyncThunk where you can reference the most updated state with getState().
Toolkit docs.
I'm using redux actions to set some contacts list. I want to get those contacts using a redux action. but all i get is the contact set in the action. Can you tell me how to get the actual state contact.
action.ts
export const setCurrentContact = (contact: IContactObject) => ({
type: 'SET_CURRENT_CONTACT',
contact,
})
export const getCurrentContact = () => ({
type: 'GET_CURRENT_CONTACT',
contact: { ID: "", Name: "", Email: "", Mobile: "", Landline: "", Website: "", Address: "" },//when using dispatch i get this contact i.e blank details
})
reducer.ts
const initialContact=[{ ID: "507", Name: "xander", Email: "xander.cage#gmail.com", Mobile: "9999999999", Landline: "4026241799", Website: "www.xandercage.com", Address: "california" }]
export const currentContact = (state = initialContact, action) => {
switch (action.type) {
case 'SET_CURRENT_CONTACT':
{
return [
{
ID: action.contact.ID,
Name: action.contact.Name,
Email: action.contact.Email,
Mobile: action.contact.Mobile,
Landline: action.contact.Landline,
Website: action.contact.Website,
Address: action.contact.Address,
}
]
}
case 'GET_CURRENT_CONTACT':
{
console.log(state[0]);
return state
}
default:
return state
}
}
somefile.ts
interface IDispatchFromProps
{
setCurrentContact: any,
getCurrentContact: any,
}
function mapDispatchToProps(dispatch): IDispatchFromProps
{
return{
setCurrentContact: (contact:IContactObject)=>{dispatch(setCurrentContact(contact))},
getCurrentContact: ()=>{dispatch(getCurrentContact())},//this should give me the initial data
}
}
expected output:
getCurrentContact() gives intial data set in reducer.ts
actual output:
data set in contact key of action.ts
The problem is that when you dispatch 'SET_CURRENT_CONTACT' action type, you crush the existing state.
instead of :
return [
{
ID: action.contact.ID,
...rest of your payload
}
]
do this :
return [
...state,
{
ID: action.contact.ID,
...rest of your payload
}
]
Hope it helps !