In the react redux doc todo example, Dan passes the action with type TOGGLE_TODO to the todos which then passes it on to each individual todo. I notice that his logic was checking for the todo.id was in the todo reducer. Couldn't this logic have been done in the todos as well? To me, it would seem better to take care of the logic at a higher level as your iterating through each todo rather than passing the work to every todo and having them figure out if they need to toggle or now. Is there a reason why Dan did it this way?
const todo = (state = {}, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
id: action.id,
text: action.text,
completed: false
}
case 'TOGGLE_TODO':
if (state.id !== action.id) {
return state
}
return Object.assign({}, state, {
completed: !state.completed
})
default:
return state
}
}
const todos = (state = [], action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
todo(undefined, action)
]
case 'TOGGLE_TODO':
return state.map(t =>
todo(t, action)
)
default:
return state
}
}
export default todos
I think you are right, if you take a look at todomvc example from redux source code repository, you'll see only one todos reducer.
The docs may be a bit outdated or such nested reducers may be just an example of its possibilities.
It's just one possible approach to structuring the reducer logic. In this case, it looks like Dan chose to define a function to handle the case of updating a single todo, and was then able to reuse that function for multiple cases by using it within higher-level iteration logic.
Related
I have a larger reducer, and I want to add another slice to it that will be fairly complicated unto itself. For example, here's a large, already complex reducer:
const initialState = {
bigchunk1: { ...someObject },
bigchunk2: true,
bigchunk3: [...somevalues],
bigchunk4: 'etc'
}
function mainReducer(state = initialState, action){
switch(action.type){
case Actions.CASE1:
return {
...state,
bigchunk1: {
...state.bigchunk1;
somevalue: action.payload.this_is_already_enough_nesting
}
}
case Actions.CASE2:
return {
...state,
bigchunk4: 'this goes on for awhile'
}
// lots more cases
default:
return state
}
}
I want to add a new slice to state, so the whole initialState would end up in the store looking like this:
const initialState = {
bigchunk1: { ...someObject },
bigchunk2: true,
bigchunk3: [...somevalues],
bigchunk4: 'etc',
newslice: { ...someComplicatedThing }
}
But rather than have to write all my new cases into my original reducer (along with a very messy level of nesting and spread operators), I want to write a new reducer that handles just the cases pertinent to newslice:
const initialState = { ...someComplicatedThing }
function newSliceReducer(state = initialState, action){
switch(action.type}{
// cases here
}
}
I am aware of combineReducers, but I'm having a hard time thinking of how to apply that in this scenario. combineReducers can take these two reducers and place them as siblings, but how can I combine these so that newSliceReducer and its associated state becomes a child of mainReducer under the name newslice? I feel like this should be simple, but its escaping me right now. I have been reading the redux docs, but I'm not seeing the answer clearly. I do not want to use redux-toolkit or any outside libraries.
In the default switch case, you can do
default:
let newSlice = newSliceReducer(state.newslice, action)
if (newSlice !== state.newslice) {
return {...state,newslice:newSlice}
}
return state
You can think of Redux as a coding pattern instead of library.
A reducer's job is to update the state and return new object if changed else return old object. If a new object is returned, react assumes something changed and a refresh is triggered.
Now, what you have is a "newSliceReducer" whose state is just a part of main reducer. So, I followed above rules and called the new reducer with state as smaller part of the original state and checked the result if it changed. If yes, I created a new object with updated state else return old state.
It is important to check if newSlice actually changed.
According to the Redux documentation, it seems to be a standard practice to set an initialState on your reducer. However this initialState needs to be maintained and if the state is being populated based on an API response, then you may have the initial state out of sync with the API response. This is especially true in cases where the state is made up of nested objects.
Is it to avoid null-checking (sometimes the initial state is set to null), are there any performance benefits? Does it improve code readability?
Taken from Redux docs:
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
}
Then in our reducer we may have an action which replaces the value in the state (for example based on an API response). Such as:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos
})
default:
return state
}
}
However the same behaviour may be achieved without using an initialState, by checking the state in the component (or selector).
const MyComponent = ({todos}) => {
if (!todos) { // if we do not have an initialState, todos will be undefined if SET_TODOS hasn't been called
return null;
}
return <div>{todos.map(n => ...)}</div>
}
If the API returns a new property (notes), we would need to update as follows:
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: [],
notes: [] // <-----
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos,
})
case SET_NOTES:
return Object.assign({}, state, {
notes: action.notes,
})
default:
return state
}
}
This is further complicated in cases when you have nested objects. If the todos has a child property subtasks: [], why are we not setting an initial state for it?
I think it's more of a convenience than anything else. I highly doubt there is any performance implications. Also while in my personal opinion it is cleaner to add all the properties you expect upfront, you are not really required to add it to the initial state. You can simply add it when returning the new state, obviously if you're using typescript this is a different story.
const initialState = {
visibilityFilter: VisibilityFilters.SHOW_ALL,
todos: []
// notes: [] you don't necessarily need to add it to the initial state
}
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_TODOS:
return Object.assign({}, state, {
todos: action.todos,
notes: action.notes // <-----
})
default:
return state
}
}
Having an initial state helps to have "cleaner" code. Like Brian Thompson said in the comments, code is more predictable if your data structure stays consistent.
It is also recommended (by some ppl) to avoid having multiples returns in one function. It may not be the best perf-wise, but it might be the easiest to read.
So having to do an early return in your component might not be the cleanest way to achieve the behavior your want.
That being said, if your implementation works, why not use it ? Well i think it's best when working as a team to stick to the conventions as much as possible.
I have been using Redux for a while, but I still can't see the point of actions and reducers.
As described in the docs, a reducer can be summarised as (previousState, action) => newState. The same principle applies for React's useReducer.
So this reducer function basically handles all actions, which seems like a violation of the Single Responsibility Principle. I'm sure that there's a good reason to do it this way, but I don't see it.
It would make more sense to me to just have a function per action. So instead of having an ADD_TODO action you would have a addTodo(previousState, todoText) => newState function. This would reduce (no pun intended) a lot of boilerplate code and might even give a slight performance improvement as you no longer need to switch through the action types.
So my question is: What's the advantage of having a reducer as opposed to a single-action function?
So if your question is why do we use reducers at all?
The real boilerplate of Flux is conceptual: the need to emit an
update, the need to register the Store with a Dispatcher, the need for
the Store to be an object (and the complications that arise when you
want a universal app).
That is a fundamental design choice redux has made as it is inspired from flux.
If you do not like the switch cases, and there by the reducer size. You can have something like this below:
export const todos = createReducer([], {
[ActionTypes.ADD_TODO]: (state, action) => {
const text = action.text.trim()
return [...state, text]
}
})
Above is a function that lets us express reducers as an object mapping from action types to handlers.
createReducer can be defined as :
function createReducer(initialState, handlers) {
return function reducer(state = initialState, action) {
if (handlers.hasOwnProperty(action.type)) {
return handlers[action.type](state, action)
} else {
return state
}
}
}
You can read more on this here
Actually it is a silly design and makes no sense.
It is just a kind of over-engineering. Because in most case, you could have just used a simpler function to update a state instead of doing it a silly way by making it two functions (reducer and action).
By adopting redux in your project, you may write tons of reducer and action codes which looks extremely ugly and difficult to do code reviews
Try to imagine, if useState function is design by redux way!
const [todos, setTodos] = useState([])
this one line code now would looks like this(tons of ugly codes):
export interface TODO {
name: string
}
export interface ITodosAction {
type:
| "SET_TODOS";
payload: {
todos?: TODO[];
};
}
export default function todosReducer(
state: TODO[] = [],
action: ITodosAction
): TODO[] {
export function setTodos(todos: TODO[]): ITodosAction {
return {
type: "SET_TODOS",
payload: { todos }
};
}
const todos = useSelector(state=>state.todos)
dispatch(setTodos(["A", "B"]));
if useState became like this, we would have to suffer using react (thanks to react's team for not adopting an over-engineering and silly design like redux).
Remember: never try to figure out why this design is good when it has already brought you trouble. We use redux not because redux is good (instead it is a worst design i've ever seen) but because we don't have many choices
I think you're not appreciating how flexible reducers are, you can create as many of them as you like:
const initialState = {
key: 'someValue',
key2: 'someValue',
key3: {
key31: 'someValue',
key32: 'someValue',
key33: []
}
}
// all the below reducers can live in separate files and only handle a single action, if they want, no switches involved at all
const key = (state = initialState.key, action) => { /* the logic to be performed at 'key' */ }
const key2 = (state = initialState.key, action) => { /* the logic to be performed at 'key2' */ }
const key31 = (state = initialState.key3.key31, action) => { /* the logic to be performed at 'key3.key31' */ }
const key32 = (state = initialState.key3.key32, action) => { /* the logic to be performed at 'key3.key32' */ }
const key33 = (state = initialState.key3.key33, action) => { /* the logic to be performed at 'key3.key33' */ }
const key3 = combineReducers({key31,key32,key33})
// this is the top level reducer for your store
const myTopLevelReducer = combineReducers({key,key2,key3});
I want to display quick flash animations on certain events (eg. a red border flash for each incorrect keystroke).
To do this with css animations, I need to remove and add the animation class each time I want to trigger the flash. (Unless there's another way to retrigger an animation?).
There are a few suggestions for doing this on this github thread: https://github.com/facebook/react/issues/7142
However, in my case the state that triggers the flash is the redux state. And in many cases the state hasn't actually changed, so it doesn't cause a rerender.
Here's the best solution I've got, which involves setting a random number to force a re-render. Is there a better way to do this?
reducer.js
//Reducer function to update redux state
function setError(state, action) {
state.hasError = true;
state.random = Math.random();
return state;
}
export default function allReducers(state = initialState, action) {
switch (action.type) {
case ActionTypes.SUBMIT_VALUE_BUTTON:
return Object.assign({}, state, setError(state, action));
default:
return state;
}
}
react component and container
const mapStateToProps = (state, ownProps) => {
return {
random: state.random,
hasError: state.hasError,
}
}
componentWillReceiveProps() {
this.setState({hasError: this.props.hasError});
setTimeout(() => {
this.setState({hasError: false});
}, 300)
}
render() {
return <div className = {`my-component ${this.state.hasError ? 'has-error':''}`} />;
}
Edit: It's worth noting that the redux documentation says that you shouldn't call non-pure functions like Math.random in a reducer method.
Things you should never do inside a reducer:
Call non-pure functions, e.g. Date.now() or Math.random().
Your code has a few problems in it, I'll go one by one...
You can't mutate the state object on the reducer. Here it is from the redux docs:
Note that:
We don't mutate the state. We create a copy with Object.assign().
Object.assign(state, { visibilityFilter: action.filter }) is also
wrong: it will mutate the first argument. You must supply an empty
object as the first parameter. You can also enable the object spread
operator proposal to write { ...state, ...newState } instead.
In your code setError receives the state as a prop and mutates it. setError should look like this:
function setError(state, action) {
let newState = Object.assign({}, state);
newState.hasError = true;
newState.random = Math.random();
return newState;
}
The second problem might be because there's some code missing but I cant see when your'e changing your state back to no errors so the props doesnt really change.
In your componentWillReceiveProps your referencing this.props instead of nextProps.
componentWillReceiveProps should look like this:
componentWillReceiveProps(nextProps) {
if (nextProps.hasError !== this.props.hasError && nextProps.hasError){
setTimeout(() => {
// Dispatch redux action to clear errors
}, 300)
}
}
And in your component you should check for props and not state as getting props should cause rerender (unless the render is stopped in componentShouldUpdate):
render() {
return <div className={`my-component ${this.props.hasError ? 'has-error':''}`} />;
}
I have an action FETCH_HABITS_SUCCESS that's called with the right data:
but it affects a reducer that isn't listening to it:
Dates reducer doesn't listen to FETCH_HABIT_SUCCESS. The data shown in the diff from the previous image should be the one from UNMARK_SUCCESS (two actions later in the execution timeline).
For some reason, the UNMARK_SUCCESS action real modifications take place when FETCH_HABIT_SUCCESS is called, at least that's what the dev tools show. I have checked that all my reducers don't mutate state. I always make a copy of the state. People at Reactiflux have checked that I don't mutate state too.
Code:
dates reducer: https://www.pastiebin.com/5a03538e98322
habits reducer:
export default function habits(state = {}, action) {
const newState = { ...state };
switch (action.type) {
case 'FETCH_HABITS_SUCCESS':
action.habits.forEach(habit => { newState[habit.id] = habit; });
return newState;
case 'ADD_HABIT_SUCCESS':
case 'EDIT_HABIT_SUCCESS':
newState[action.habit.id] = action.habit;
return Object.assign({}, state, newState);
case 'DELETE_HABIT_SUCCESS':
delete newState[action.habit_id];
return newState;
default: return state;
}
}
Does anyone have any idea of what may I be doing wrong?