state gets different values in react redux - reactjs

the title may be misleading but here is what happened:
reducer.js:
// initial state
const initialState = {
notes: [
{
content: "reducer defines how redux store works",
important: true,
id: 1,
},
{
content: "state of store can contain any data",
important: false,
id: 2,
},
],
filter: "IMPORTANT",
};
// reducer
const noteReducer = (state = initialState, action) => {
switch (action.type) {
case "NEW_NOTE":
console.log("state", state);
return state.notes.concat(action.data);
// ...
}
const generateId = () => Math.floor(Math.random() * 1000000);
// action
export const createNote = (content) => {
return {
type: "NEW_NOTE",
data: {
content,
important: false,
id: generateId(),
},
};
};
in index.js:
const reducer = combineReducers({
notes: noteReducer,
filter: filterReducer,
});
const store = createStore(reducer, composeWithDevTools());
//dispatch a note from index.js
//it works here
store.dispatch(
createNote("combineReducers forms one reducer from many simple reducers")
);
returns in the console.log("state", state); in reducer.js:
state
{notes: Array(2), filter: 'IMPORTANT'}
filter: "IMPORTANT" // 'filter' is here
notes: (2) [{…}, {…}]
[[Prototype]]: Object //prototype is object
Here createNote is successful.
However, when creating a new note through:
const NewNote = (props) => {
const dispatch = useDispatch();
const addNote = (event) => {
event.preventDefault();
const content = event.target.note.value;
event.target.note.value = "";
// createNote does not work here
dispatch(createNote(content));
};
return (
<form onSubmit={addNote}>
<input name="note" />
<button type="submit">add</button>
</form>
);
};
Here the console.log("state", state); returns:
state
(3) [{…}, {…}, {…}]
0: {content: 'reducer defines how redux store works', important: true, id: 1}
1: {content: 'state of store can contain any data', important: false, id: 2}
2: {content: 'combineReducers forms one reducer from many simple reducers', important: false, id: 824517}
length: 3
// 'filter' is missing
[[Prototype]]: Array(0) // state prototype changed to array
In which the filter is gone from the state, so the creation is not successful.
In short, store.dispatch( createNote("...") ); works but not dispatch(createNote(content));.
The reason seems to be that noteReducer received different states. But in both cases filter is not specified.
I wonder why this happens and how to solve it?

as we know when you are using a reducer the reducer takes 2 parameters one for initial stat and the other for action.
any action will be run in the reducer you need to save the old state
by used spread operator {...state}
case "NEW_NOTE":
console.log("state", state);
{...state, notes: state.notes.concat(action.data)}

found the issue.
noteReducer should be:
const noteReducer = (state = initialState, action) => {
switch (action.type) {
case "NEW_NOTE":
return { ...state, notes: state.notes.concat(action.data) };
//...
}
just found out that above is a wrong fix. the right one is actually is:
const noteReducer = (state = initialState.notes, action) => {
otherwise noteReducer is changing the filter as well. but it should be changing the 'note' part only.

Related

react-redux state is always same and not chainging

I'm using redux and my reducer function is called in every time the dispatch called but the state is not updating. and there is no difference between the first state and the next state.
ArtclesReducer.ts
const defaultState: Articles = {
articles: [{token: "teken", title: "text", featureImageUrl: ""}],
}
export const articlesReducer: Reducer<Articles, any> = (state = defaultState, action: ArticlesActionTypes) => {
let nextState: Articles = {
articles: state.articles,
}
switch (action.type) {
case ADD_ARTICLES :
let allArticles = [...state.articles, ...action.payload]
return {
articles: [{title: "", token: "", featureImageUrl: ""}, {
title: "",
token: "",
featureImageUrl: ""
}, {title: "", token: "", featureImageUrl: ""}, {title: "", token: "", featureImageUrl: ""}]
}
case UPDATE_ARTICLE:
console.log("in update article")
for (let i = 0; i < nextState.articles.length; i++) {
if (nextState.articles[i].token == action.payload.token) {
nextState.articles[i] = action.payload;
break;
}
}
break;
case DELETE_ARTICLE:
console.log("in delete article")
nextState.articles = nextState.articles.filter(value => {
return value.token != action.payload;
})
break;
default:
}
return nextState;
}
as shown up I return a non-empty state.
as you see the state it becomes the same and not updating
Redux Toolkit
If you are unsure about how to update the state without mutating it, you can save yourself a lot of frustration by using Redux Toolkit. The toolkit makes it so you can write the code as if you were mutating the state (it handles the immutability issue behind the scenes).
Here's how this reducer would look with the createReducer utility:
const articlesReducer = createReducer(defaultState, {
[ADD_ARTICLES]: (state, action) => {
// We don't return anything. We just mutate the passed-in draft state.
state.articles.push(action.payload);
},
[UPDATE_ARTICLE]: (state, action) => {
// Find which article we are updating
const index = state.articles.findIndex(
article => article.token === action.payload.token
);
// Replace that index with the new article from the payload
state.articles[index] = action.payload;
},
[DELETE_ARTICLE]: (state, action) => {
// We replace the articles array with a filtered version
state.articles = state.articles.filter(
article => article.token === action.payload
);
}
});
Most people don't use createReducer directly because there is an even better utility createSlice that creates the action names and action creator functions for you!
Vanilla Redux
Of course you can still do this the "old-fashioned" way. But you need to be sure that you never mutate any part of the state and that every case returns a complete state.
nextState.articles[i] = action.payload is actually a mutation even though nextState is a copy because it is a shallow copy so the articles property points to the same array as the current state.
I do not recommend this approach unless you are confident that you know what you are doing, but I want to include a correct version to show you how it is done.
export const articlesReducer: Reducer<Articles, any> = (state = defaultState, action: ArticlesActionTypes) => {
switch (action.type) {
case ADD_ARTICLES:
return {
...state,
articles: [...state.articles, ...action.payload]
};
case UPDATE_ARTICLE:
return {
...state,
articles: state.articles.map((article) =>
article.token === action.payload.token ? action.payload : article
)
};
case DELETE_ARTICLE:
return {
...state,
articles: state.articles.filter((article) =>
article.token !== action.payload
)
};
default:
return state;
}
};
Note: Writing ...state like you see in most examples is technically not necessary here since articles is the only property in your state so the there are no other properties to be copied by ...state. But it might be a good idea to include it anyways in case you want to add additional properties in the future.

Redux Toolkit: 'Cannot perform 'set' on a proxy that has been revoked'

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

redux-toolkit sharing state between reducer

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.

Problem with Reducer that contains few different values

I'm kind of new to React.js & Redux, so I have encountered a problem with Reducers.
I am creating a site that have a main "Articles" page, "Question & Answers" page, I created for each one a separate Reducer that both work just fine.
The problem is in "Main Page" which contains a lot of small different pieces of information, and I don't want to create each little different piece of information its on Reducer, so I am trying to create one Reducer which will handle a lot of very small different pieces of information, and I can't make that work, inside the main "Content" object, I put 2 Key Value Pairs that each have an array, one for each different information, one is "Features" info, and one for the "Header" info.
This is the error that I'm getting:
Uncaught TypeError: Cannot read property 'headerContent' of undefined
at push../src/reducers/ContentReducer.js.__webpack_exports__.default (ContentReducer.js:15)
I am not sure what's the problem, maybe my code is wrong or maybe my use of the spread operator, any solution?
I have added the necessary pages from my code:
ACTIONS FILE
export const addFeatureAction = (
{
title = 'Default feature title',
feature = 'Default feature',
} = {}) => ({
type: 'ADD_FEATURE',
features: {
id: uuid(),
title,
feature
}
})
export const addHeaderAction = (
{
title = 'Default header title',
head = 'Default header',
} = {}) => ({
type: 'ADD_HEADER',
header: {
id: uuid(),
title,
head
}
})
REDUCER FILE:
const defaultContentReducer = {
content: {
featuresContent: [],
headerContent: [],
}
}
export default (state = defaultContentReducer, action) => {
switch(action.type) {
case 'ADD_FEATURE':
return [
...state.content.featuresContent,
action.features
]
case 'ADD_HEADER':
return [
...state.content.headerContent,
action.header
]
default:
return state
}
}
STORE FILE:
export default () => {
const store = createStore(
combineReducers({
articles: ArticleReducer,
qnaList: QnaReducer,
content: ContentReducer
})
);
return store;
}
The reducer function is supposed to return the next state of your application, but you are doing a few things wrong here, you are returning an array, a piece of the state and not the state object, I would suggest you look into immer to prevent this sort of errors.
Simple fix:
export default (state = defaultContentReducer, action) => {
switch(action.type) {
case 'ADD_FEATURE':
return {...state, content: {...state.content. featuresContent: [...action.features, ...state.content.featuresContent]}}
// More actions are handled here
default:
return state
}
}
If you use immer, you should have something like this
export default (state = defaultContentReducer, action) => {
const nextState = produce(state, draftState => {
switch(action.type) {
case 'ADD_FEATURE':
draftState.content.featuresContent = [...draftState.content.featuresContent, ...action.features]
});
break;
default:
break;
return nextState
}

Filter products depend on another ACTION in React-native Redux

I have an app which get all categories and products from the server with Redux ACTIONS. I need to filter products with a category Id. after load data action is complete, i call another action to filter products but i'm a little bit confused.
There is codes of few parts of the app:
ProductsActions:
export const GET_INITIAL_PRODUCTS_DATA = "GET_INITIAL_PRODUCTS_DATA";
export const GET_INITIAL_PRODUCTS_DATA_RESULT = "GET_INITIAL_PRODUCTS_DATA_RESULT";
export const GET_INITIAL_PRODUCTS_DATA_ERROR = "GET_INITIAL_PRODUCTS_DATA_ERROR";
export const FILTER_PRODUCTS_BY_CATEGORY_ID = "FILTER_PRODUCTS_BY_CATEGORY_ID";
export const getInitialProductsData = () => ({
type: GET_INITIAL_PRODUCTS_DATA
});
export const filterProductsByCategoryId = categoryId => ({
type: FILTER_PRODUCTS_BY_CATEGORY_ID,
categoryId
});
ProductsReducers:
import {
GET_INITIAL_PRODUCTS_DATA,
GET_INITIAL_PRODUCTS_DATA_RESULT,
GET_INITIAL_PRODUCTS_DATA_ERROR,
FILTER_PRODUCTS_BY_CATEGORY_ID
} from "../actions/products";
const initialState = {
isFetching: false,
data: {},
error: null
};
const filterProductsByCategoryId = (state, action) => {
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case GET_INITIAL_PRODUCTS_DATA:
return {
...state,
isFetching: true
};
case GET_INITIAL_PRODUCTS_DATA_RESULT:
return {
...state,
isFetching: false,
data: action.result
};
case GET_INITIAL_PRODUCTS_DATA_ERROR:
return {
...state,
isFetching: false,
error: action.error
};
case FILTER_PRODUCTS_BY_CATEGORY_ID:
return {
...state,
data: filterProductsByCategoryId(state, action.categoryId)
};
default:
return state;
}
};
export default reducer;
And there is my code to call filter action:
filterProducts = (title = "A") => {
const _categories = Object.values(this.props.categories);
const selectedCategory = _categories.find(
category => category.title === title
);
this.props.dispatch(filterProductsByCategoryId(selectedCategory.id));
My questions is:
A) Is there is a way to filter my data and display them in UI and refresh them without using ACTIONS way??
B) If A's answer is No!, How can i get my state.data and filter them in FILTER_PRODUCTS_BY_CATEGORY_ID?
Thanks.
You can use the Array.prototype.filter() to return filtered result.
keep in mind that this will return an array and not a single value, which is a good thing if you are using this filter within your reducer. because your reducer's shape is an array and not an object.
Running example:
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, action) => {
return state.filter(c => c.id === action.categoryId);
};
const result = filterProductsByCategoryId(myData, {categoryId: 2});
console.log(result);
I think it is more appropriate to create a selector for a singular product that will handle this kind of action, this way you will be able to return an object instead of an array with one product in it.
Not to mention the benefits of using reselect to do some memoizations.
For this task you can use the Array.prototype.find():
const myData = [{
name: 'some name',
id: 1
}, {
name: 'some name2',
id: 2
}, {
name: 'some name3',
id: 3
}, {
name: 'some name4',
id: 4
}]
const filterProductsByCategoryId = (state, id) => {
return state.find(c => c.id === id);
};
const result = filterProductsByCategoryId(myData, 2);
console.log(result);

Resources