I am new to redux and I was testing it by creating a theme toggler and a counter.
the problem is everytime i click to increment the counter that works fine but when i try to toggle the dark theme the count resets to 0 and vice versa. I am using combineReducers which holds the themeReducer and counterReducer.
Why is the counter getting reset on state change? what am i doing wrong?
here is some code:
Actions:
export function increment() {
return {
type: 'INCREMENT',
};
}
export function decrement() {
return {
type: 'DECREMENT',
};
}
//different file
export function darkTheme() {
return {
type: 'DARK',
};
}
Reducers:
export default function counterReducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return 0;
}
}
//different file
export default function themeReducer(state = false, action) {
switch (action.type) {
case 'DARK':
return !state;
default:
return false;
}
}
im calling it like this :
const dispatch = useDispatch();
<button onClick={() => dispatch(increment())}> +</button>
You have to always return state in the default case, in both reducers: every reducer will always be called with every action, so they have to do no modification to the existing state in the default case.
All that said, I want to make you aware that you are learning "vanilla redux" here, which is not the recommended style of redux for production use any more. So while that is great to get an understanding of the underlying concepts, you should not really write code like this in the end. Please follow the official redux tutorials to get an understanding on how to write modern redux. (The fundamentals tutorial will get you started with the style you are using right now and then move into more modern territory, the essentials tutorial will start directly with modern redux - both ways are valid for learning redux)
Related
I have come across two kinds of reducer design for handling a large state within a single module.
The first approach is to have all the variables inside a single large state and have one reducer function.
const initialState = {
results: [],
pagination: {},
filters: [],
appliedFilters = [],
}
const reducer = (st = { ...initialState }, action) => {
const state = st;
switch (action.type) {
case 'SEARCH':{
return {
...state,
results: action.results,
pagination: action.pagination,
filters: action.filters,
appliedFilters: action.appliedFilters
},
case 'APPLY_FILTER':{
return {
...state,
results: action.results,
pagination: action.pagination,
filters: action.filters,
appliedFilters: action.appliedFilters
},
case 'PAGINATE':{
return {
...state,
results: action.results,
pagination: action.pagination,
}
}
The second approach is to have multiple reducers for the sub items in the data.
export function applications(state = [], { type, results}) {
switch (type) {
case SEARCH:
return results;
case INIT_RESULTS:
return [];
default:
return state;
}
}
export function pagination(state = null, { type, paginationData }) {
switch (type) {
case SEARCH:
return paginationData;
default:
return state;
}
}
export function filters(state = [], { type, filterData }) {
switch (type) {
case SEARCH:
return filterData;
case UPDATE_FILTERS:
return filterData;
default:
return state;
}
}
I think both have their own pros and cons. Considering scalability and modularization which one is a better pick?
Generally, both of these are very far off our official recommendations.
you should have a "slice" reducer for each sub-state (that rules out your first option
you should not treat reducers as "setting a value", but move the whole "calculating how to get the value" into the reducer and handle your action as just "describing an event that happened"
you should be using the official Redux Toolkit which we are recommending & teaching as the default way of writing Redux sinde 2019. Seriously, look at it. It is about 1/4 of the code. No more switch..case reducers or ACTION_TYPES.
Please give the Redux Style Guide a read and to learn modern Redux with Redux Toolkit, please follow the official Redux Tutorial
Issue (tl;dr)
How can we create a custom redux-orm reducer with redux-toolkit's createSlice?
Is there a simpler, recommended, more elegant or just other solution than the attempt provided in this question?
Details
The example of a custom redux-orm reducer looks as follows (simplified):
function ormReducer(dbState, action) {
const session = orm.session(dbState);
const { Book } = session;
switch (action.type) {
case 'CREATE_BOOK':
Book.create(action.payload);
break;
case 'REMOVE_AUTHOR_FROM_BOOK':
Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
break;
case 'ASSIGN_PUBLISHER':
Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
break;
}
return session.state;
}
It's possible to simplify reducers with the createSlice function of redux-toolkit (based on the redux-toolkit usage-guide):
const ormSlice = createSlice({
name: 'orm',
initialState: [],
reducers: {
createBook(state, action) {},
removeAuthorFromBook(state, action) {},
assignPublisher(state, action) {}
}
})
const { actions, reducer } = ormSlice
export const { createBook, removeAuthorsFromBook, assignPublisher } = actions
export default reducer
However, at the beginning of redux-orm reducer we need to create a session
const session = orm.session(dbState);
then we do our redux-orm reducer magic, and at the end we need to return the state
return session.state;
So we miss something like beforeEachReducer and afterEachReducer methods in the createSlice to add this functionality.
Solution (attempt)
We created a withSession higher-order function that creates the session and returns the new state.
const withSession = reducer => (state, action) => {
const session = orm.session(state);
reducer(session, action);
return session.state;
}
We need to wrap every reducer logic in this withSession.
import { createSlice } from '#reduxjs/toolkit';
import orm from './models/orm'; // defined elsewhere
// also define or import withSession here
const ormSlice = createSlice({
name: 'orm',
initialState: orm.session().state, // we need to provide the initial state
reducers: {
createBook: withSession((session, action) => {
session.Book.create(action.payload);
}),
removeAuthorFromBook: withSession((session, action) => {
session.Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
}),
assignPublisher: withSession((session, action) => {
session.Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
}),
}
})
const { actions, reducer } = ormSlice
export const { createBook, removeAuthorsFromBook, assignPublisher } = actions
export default reducer
This is a fascinating question for me, because I created Redux Toolkit, and I wrote extensively about using Redux-ORM in my "Practical Redux" tutorial series.
Off the top of my head, I'd have to say your withSession() wrapper looks like the best approach for now.
At the same time, I'm not sure that using Redux-ORM and createSlice() together really gets you a lot of benefit. You're not making use of Immer's immutable update capabilities inside, since Redux-ORM is handling updates within the models. The only real benefit in this case is generating the action creators and action types.
You might be better off just calling createAction() separately, and using the original reducer form with the generated action types in the switch statement:
export const createBook = createAction("books/create");
export const removeAuthorFromBook = createAction("books/removeAuthor");
export const assignPublisher = createAction("books/assignPublisher");
export default function ormReducer(dbState, action) {
const session = orm.session(dbState);
const { Book } = session;
switch (action.type) {
case createBook.type:
Book.create(action.payload);
break;
case removeAuthorFromBook.type:
Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
break;
case assignPublisher.type:
Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
break;
}
return session.state;
}
I see what you're saying about adding some kind of "before/after" handlers, but that would add too much complexity. RTK is intended to handle the 80% use case, and the TS types for createSlice are already incredibly complicated. Adding any more complexity here would be bad.
I came across this question looking to combine the benefits of redux-toolkit
and redux-orm. I was able to come up with a solution I've been pretty happy
with so far. Here is what my redux-orm model looks like:
class Book extends Model {
static modelName = 'Book';
// Declare your related fields.
static fields = {
id: attr(), // non-relational field for any value; optional but highly recommended
name: attr(),
// foreign key field
publisherId: fk({
to: 'Publisher',
as: 'publisher',
relatedName: 'books',
}),
authors: many('Author', 'books'),
};
static slice = createSlice({
name: 'BookSlice',
// The "state" (Book) is coming from the redux-orm reducer, and so will
// never be undefined; therefore, `initialState` is not needed.
initialState: undefined,
reducers: {
createBook(Book, action) {
Book.create(action.payload);
},
removeAuthorFromBook(Book, action) {
Book.withId(action.payload.bookId).authors.remove(action.payload.authorId);
},
assignPublisher(Book, action) {
Book.withId(action.payload.bookId).publisherId = action.payload.publisherId;
}
}
});
toString() {
return `Book: ${this.name}`;
}
// Declare any static or instance methods you need.
}
export default Book;
export const { createBook, removeAuthorFromBook, assignPublisher } = Book.slice.actions;
The redux-toolkit slice is created as a static property on the class, and then
the model and its actions are exported in a manner similar to Ducks
(ORMDucks??).
The only other modification to make is to define a custom updater for
redux-orm's reducer:
const ormReducer = createReducer(orm, function (session, action) {
session.sessionBoundModels.forEach(modelClass => {
if (typeof modelClass.slice.reducer === 'function') {
modelClass.slice.reducer(modelClass, action, session);
}
});
});
See a more complete example here:
https://gist.github.com/JoshuaCWebDeveloper/25a302ec891acb6c4992fe137736160f
Some Notes
#markerikson makes a good point about some of the features of redux-toolkit
not being used since redux-orm is managing the state. For me, the two
greatest benefits of using this method are not having to wrangle a whole
bunch of action creators and not having to contend with awful switch
statements :D.
I am using the stage 3 class fields and static class features proposals. (See
https://babeljs.io/docs/en/babel-plugin-proposal-class-properties). To make
this ES6 compatible, you can easily refactor the model class to define its
static props using the current syntax (i.e. Book.modelName = 'Book';).
If you decide to mix models like the one above with models that don't define
a slice, then you'll need to tweak the logic in the createReducer updater
slightly.
For a real world example, see how I use the model in my project here:
https://github.com/vallerance/react-orcus/blob/70a389000b6cb4a00793b723a25cac52f6da519b/src/redux/models/OrcusApp.js.
This project is still in the early stages. The largest question in my mind is
how well this method will scale; however, I am optimistic that it will continue
to provide numerous benefits as my project matures.
Try using normalized-reducer. It's a higher-order-reducer that takes a schema describing the relationships, and returns a reducer, action, and selectors that write/read according to the relationships.
It also integrates easily with Normalizr and Redux Toolkit.
I have hashMap in my redux store, I want change isChecked value for children id: 2. Is it good to make it on state like this (operating on state)?
My hashMap
const childrens = {
1: { name: "Test", isChecked: false },
2: { name: "test2", isChecked: false }
};
Here is my reducer
export const childrensReducer = (state = childrens, action) => {
switch (action.type) {
case "SELECT_CHILDREN":
const id = 2;
state[id].isChecked = !state[id].isChecked;
return { ...state };
}
};
The problem is that you are mutating the state in the reducer with this line:
state[id].isChecked = !state[id].isChecked;
Why immutability is required by redux can be found in official docs:
https://redux.js.org/faq/immutable-data
One way to do is: ( I expect you send id through action.id )
case "SELECT_CHILDREN":
return {
...state,
[action.id]: {
...state[action.id],
isChecked: !state[action.id].isChecked
}
};
These kind of state operations are easier when an array is used for state.
It's not a good practice to mutate the state like you did.
There are different approaches of changing the state. Take a look at the below link to get some more information and examples.
https://www.freecodecamp.org/news/handling-state-in-react-four-immutable-approaches-to-consider-d1f5c00249d5/
Its not a good practise to mutate state, since react depends on immutability for a lot of its features.
Consider for example lifecycle methods or rerender after comparing state/props(PureComponents)
The problem with mutating state is that when these values are passed as props to children and you try to take some decision on them based on whether the state has updated, the previous props and the current props both will hold the same value and hence the comparisons may fail leading to buggy application
The correct way to update state is
case "SELECT_CHILDREN":
const id = 2;
return {
...state,
[id]: {
...state[id],
isChecked: !state[id].isChecked
}
};
Gee, I feel foolish about this, but I have read every part of: http://redux.js.org/ (done the egghead tutorials, and read 4 times the FAQ at: http://redux.js.org/docs/faq/ImmutableData.html
What I did was stub one of my reducers, to always return state, and that is the only reducer being called (checked with breakpoints). Even so, my subscribe event is being called every time the reducer returns state. What Do I not understand? (Action.SetServerStats is being called at a 1Hz rate, and the subscribe is also being called at a 1Hz Rate
BTW the Chrome Redux Extension says thats states are equal, and the React Extension for Chrome with Trace React Updates, is not showing any updates.
I will be glad to remove the question, when someone clues me in. But right now, what I see each each of the reducers being called at 1Hz, and all of them returning the slice of the store that they got (state).
So do I not understand subscribe, and that it returns every time even when the store tree does not get modified (and it is up to react-redux to do shallow compare to figure out what changed if any?)
create store & subscribe
let store = createStore(reducer, initialState, composeWithDevTools(applyMiddleware(thunk)))
store.subscribe(() => console.log("current store: ", JSON.stringify(store.getState(), null, 4)))
reducers.js
import A from './actionTypes'
import { combineReducers } from 'redux'
export const GLVersion = (state = '', action) => {
switch (action.type) {
case A.SetGLVersion:
return action.payload
default:
return state
}
}
export const ServerConfig = (state = {}, action) => {
switch (action.type) {
case A.SetServerConfig: {
let { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath } = action.payload
let p = { ServerPort, UserID, PortNumber, WWWUrl, SourcePath, FMEPath }
return p
}
default:
return state
}
}
export const ServerStats = (state = {}, action) => {
switch (action.type) {
case A.SetServerStats:
return state
// let { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize } = action.payload
// let s = { WatsonInstalled, WatsonRunning, FMERunning, JobsDirSize }
// return s
default:
return state
}
}
export default combineReducers({ GLVersion, ServerConfig, ServerStats })
Correct. Redux will execute all subscription callbacks every time an action is dispatched, even if the state is not updated in any way. It is up to the subscription callbacks to then do something meaningful, such as calling getState() and checking to see if some specific part of the state has changed.
React-Redux is an example of that. Each instance of a connected component class is a separate subscriber to the store. Every time an action is dispatched, all of the wrapper components generated by connect will first check to see if the root state value has changed, and if so, run the mapStateToProps functions they were given to see if the output of mapState has changed at all. If that mapState output changes, then the wrapper component will re-render your "real" component.
You might want to read my blog post Practical Redux, Part 6: Connected Lists, Forms, and Performance, which discusses several important aspects related to Redux performance. My new post Idiomatic Redux: The Tao of Redux, Part 1 - Implementation and Intent also goes into detail on how several parts of Redux actually work.
I would like to rehydrate the whole state of my app from the Javascript code, just like auto-rehydrate works but on-demand.
Can someone show me an example of this ?
I think I need to emit a REHYDRATE action but I can't see how. And I am not sure it will do the same thing as auto-rehydrate.
If you want to programmatically replace your entire app state, it seems you can do what you want with a "plain-old" reducer, no need for plugins:
function todoApp(state = initialState, action) {
switch (action.type) {
case 'APP_STATE_REPLACE':
return action.payload.newState
default:
return state
}
}
and trigger it like this:
store.dispatch({ type: 'APP_STATE_REPLACE',
payload: { newState: { hello: 'world' } }
});