Is setting value of the localStorage a side effect? - javascript

I'm working on a React+Redux app that must save certain values from the store to localStorage. I have some utility function, that securely store values to localStorage. I have an action (thunk) and reducer.
const wallet = (state = getFromLocalStorage('wallet'), action) => {
switch(action.type) {
case 'SET_WALLET':
setToLocalStorage('wallet', action.payload); // <--- **SIDE EFFECT?**
retrun action.payload;
default:
return state;
}
}
Reducers must be pure functions. Does the getToLocalStorage function call break the purity of the reducer? And if so, where is the proper place to call this function?

As the other comment stated: yes, it's definitely a side effect. Now, it's probably not going to break anything in this case, but a reducer isn't the right place for that.
A better solution would be to either do this in a store subscription callback or a middleware. Or, even better, use one of the many store persistence libraries already available. Doing so in a thunk would also be an acceptable option, especially if you just want to persist a small amount of data rather than the entire store state.

It's definitely a side effect in FP terms, but actually there is nothing bad with your code. There is no other better workaround of sharing your state between components (or even outside react.js tree) except redux/flux maybe.
Functional programming is a good thing but you don't need to turn that into cult

Related

Redux: Is it common to do deep-compare at reducer and return original state if no change?

Action: fetch an array of data from the server.
Reducer: save an array of data to store.
My reducer saves the array of data in the expected immutable fashion:
return {
...state,
arrayOfData: fetchedArrayOfData,
};
The fetch action happens periodically, but the content of the array remains the same most of the time. However, since a new reference is saved each time, the selector for this data will be considered "changed", and doesn't memoize well if used as an input for creatorSelector.
The only solution I can think of is to perform a deep-compare at the reducer, and if the contents are the same, simply return the original state:
return state;
If this common practice/pattern?
Note: I tried to look around, and most projects are doing the same thing that I was doing (i.e. return new state object), which will do not memoize well and causes selector transformations to run.
There are some solutions like using immutableJS as mentioned in the comments but you can return your state conditionally by comparing your fetchedArrayOfData with the last one (which is stored in your state).
Assume there is a comparison function that gives two arrays and compares them.
In the reducer:
const previousFetchedData = state.fetchedArrayOfData;
const newFetchedData = action.payload.fetchedArrayOfData;
const resultOfComparision = isSameArray(previousFetchedData, newFetchedData) // true or false
if (resultOfComparision) { // case of same arrays
return state
} else { // case of different arrays
...state,
arrayOfData: fetchedArrayOfData,
};
Note 1: you can create your own comparison function but there are many nice ones in this post of StackOverflow which you can use them.
Note 2: using immutableJs and conditional returning data from reducer is common(in such scenario) and don't worry about using them.
Note 3: You can also compare your data at the component level by using the traditional way with shouldComponentUpdate.
Node 4: using middlewares like redux-saga will be useful, you can also implement the isSameArray function in the saga and then dispatch the proper action. read more on saga documentation.
Note 5: The best solution (in my thought) is to handle this case on the backend services with 304 status which means Not modified. then you can easily determine the right action according to the response status. more info on MDN documentation.

React saving array state using hooks

I'm trying to learn how to implement a list in react. The docs recommend that I use the hooks API to use state. I read through them, and I have an idea of how to implement one:
function ListReducer(list, action) {
switch(action.type) {
case 'push': {
listCopy = [...list]
listCopy.push(action.data);
return listCopy
}
}
}
const [state, dispatch] = useReducer(ListReducer, []);
However, this strikes me as very inefficient, since the entire list needs to be copied in order to return the value, and generally this only takes O(1). Is there a better way to handle this situation?
Since you're using React, there is no way to change the state of an array without iterating over the entire array - you can't, for example, just .push to the existing array, because that'd mutate the state.
There is no definitively better option than your general approach - but you can make the syntax look a bit nicer:
case 'push':
return [...list, action.data];
The efficiency of a given section of code rarely matters in real-world projects, usually. There are often one or a few sections of code that are bottlenecks, but the likelyhood that any given section you're looking at at the moment is the bottleneck is slim.
Don't worry about it until you see that your app is running slower than desirable, and when that happens, run a performance analysis to see what exactly the bottleneck is, and fix that - until then, it's often not worth taking the time to worry about.
that's the nature of using the state/reducer pattern, nothing out of the box in the JavaScript language to change/handle that...
there's a library called immer, the basic idea is that with Immer you will apply all your changes to a temporary draft, which is a proxy of the currentState. Once all your mutations are completed, Immer will produce the nextState based on the mutations to the draft state,
turning your code in:
import produce from "immer"
function ListReducer(list, action) {
switch(action.type) {
case 'push': {
let nextState = produce(list, draftState => {
draftState.push(action.data)
})
return nextState
}
}
}
const [state, dispatch] = useReducer(ListReducer, [])
when using immer, there's a significant reduction in the maintenance for your team on "are we using mutable/immutable APIs in JavaScript?"... with immer, you remove this problem

Can useReducer work with an array for state?

I've implemented a fairly simple means to add undo to useReducer with the following:
export const stateMiddleware = reducerFunc => {
return function(state = { history: [] }, action) {
return {
history:
action.type === UNDO
? drop(state.history) // lodash drop
: [reducerFunc(state.history[0], action), ...state.history],
}
}
}
and
const [state, dispatch] = useReducer(stateMiddleware(reducer), { history: [initialState] })
The state variable consists of the entire history so present state will have to be extracted from the first element.
I'm wondering whether useReducer will accept an array for the state parameter (and also welcome to comments on my approach - it avoids using what seem to be hefty packages to do something similar)
If all you are really asking is if you can initialize useReducer hook state with a plain array, then yes, you can.
To give you some feedback on your general approach, I'll quote the Redux docs, as they provide many best practices about global state management. It makes switching easier, if you need a full blown store later on. I'll assume here that you want use the useReducer not just for one local component, but a more global state tree (thanks #DrewReese for the comment on this). If it's only about one component or so, your approach is perfectly fine!
Redux docs concerning basic state shape and wether an array is possible at the top level (Link):
A Redux state usually has a plain Javascript object as the top of the state tree. (It is certainly possible to have another type of data instead, such as a single number, an array, or a specialized data structure, but most libraries assume that the top-level value is a plain object.) The most common way to organize data within that top-level object is to further divide data into sub-trees, where each top-level key represents some "domain" or "slice" of related data.
So basically, its OK, but other libraries could be possibly opiniated and expect an object, as it is by far the most likely choice.
If you implement this shape, be sure that your requirement is to always need an undo action for your whole state. Because it comes with a cost (besides mentioned 3rd party lib compatibility):
adds one additional level of nesting directly at the top level
scalability and granularity (see next point)
What, if your app gets really huge and you have hundreds of reducers. Each time, your action is dispatched, there will be a new state object put in the history array, which could get messy.
What, if you don't want a global undo, but a very fine granular one (in one particular reducer, say for one particular form element in the UI). You would have to distinguish between a global undo and a specific undo then. What instead about just one "UNDO" action which can be handled in each interested reducer in its own manner?
If that still fits your requirements, give it a try !
Cheers
It is possible, but as ford04 pointed out it may not be a great idea for a variety of reasons.
The demo below shows it works to use a plain array as the state in useReducer.
const initialState = [];
function reducer(state, action) {
switch (action.type) {
case 'increment':
return [...state, state.length + 1];
case 'decrement':
return [...state.slice(0, -1)];
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = React.useReducer(reducer, initialState);
return (
<React.Fragment>
<div>
State: {state.join()}
</div>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</React.Fragment>
);
}
ReactDOM.render(<Counter />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

What is the right way for a reducer to affect state that is far away?

I am building a React-Redux project and I am trying to be idiomatic about my usage of Redux, and avoid hacking things together in a way that makes the code difficult to maintain later. I am also new to this ecosystem.
I have a nested state that looks something like this:
{ foo: {stuff}, bar: {baz: {stuff} } }
and I use combineReducers, so that foo and bar and baz all have their own reducers to interpret relevant actions for changing their own state. But I've run into a situation where an action could, depending on the state of baz, have an implication that might be of interest to foo.
I have basically three ideas, where I hate the first one and don't know how to do the other two:
1) Make the reducers for bar/baz have access to the whole state, and ask them to be responsible about it.
This makes this exact situation easy to deal with, but it seems bad from a separation of concerns perspective.
2) Somehow have the baz reducer dispatch a relevant action that foo would then pick up on.
This makes sense to me from a descriptiveness perspective, but I don't actually know how to do it. The fact that it's not obvious makes me think Redux is against this.
3) Import some magic library that makes this simple
I don't know what library would do this, though. It doesn't seem like this is what redux-thunk does (although I'm not sure) and then I really don't know.
I would suggest you keep your reducers simple. They are there to modify the slice of state they care for, and nothing else.
If an "action" affects more than one slice, then it should dispatch multiple, "actions".. The naming can get confusing at this point, but basically redux-thunk allows you to do just this.
If you have regular action creators, eg modifyFoo & modifyBar which simply return action objects which are dispatched to the reducers, with redux-thunk enabled you can create a more complex "action" which dispatches both. eg..
function modifyFoo(options) {
return { type: "MODIFY_FOO", payload: options }};
}
function modifyBar(options) {
return { type: "MODIFY_BAR", payload: options }};
}
function modifyFooBar(options) {
return (dispatch, getState) => {
const appState = getState();
dispatch(modifyFoo(options.foo, appState.baz));
dispatch(modifyBar(options.bar));
}
}
Using getState you have access to the full state of the app if you need it.
tl;dr -- I've decided to use redux-loop, which lets you describe side effects of a reducer without making the reducer pure/stateless.
Why I did not use redux-thunk for this:
The most common approach (based on my googling), represented in #lecstor's answer, is to use redux-thunk. In the typical redux-thunk code organization, you have to figure out everything that's going to happen first, then dispatch some numbers of actions, async operations, etc. through a thunk.
The downside in this case is the second action is (a) conditional on the state after the first action triggers, and (b) cannot be derived from the state itself, but must be derived from the fact that we have this state after this action.
So with redux-thunk you either have to duplicate the logic of the reducer (bad), or do all the business logic before the reducers, then launching a bunch of actions that describe state changes.
This is all to say, redux-thunk does its magic before the reducer happens.
Why I think redux-loop was a better fit:
On the other hand, redux-loop does magic after the reducer happens, allowing the reducer to describe those effects (including kicking off async actions, dispatching further actions, etc.) without breaking statelessness.
For the historical record, this means that in the foo reducer above you can do:
// ... nextState is the new fooState
if (isWeird(nextState)) {
return loop(nextState, Cmd.action(makeWeirdAction()));
} else {
return nextState;
}
so that all other reducers can interpret or ignore weird action as they please, instead of making these decisions all at once in the action creator.

Why should objects in Redux be immutable?

Why should objects in Redux be immutable?
I know that some frameworks such as Angular2 will use onPush and can take advantage of immutability to compare states of views for faster rendering, but I am wondering if there are other reasons as Redux is framework agnostic and yet it mentions within its own docs to use immutability (regardless of the framework).
Appreciate any feedback.
Redux is a small library that represents state as (immutable) objects. And new states by passing the current state through pure functions to create an entirely new object/application states.
If your eyes-glazed over there don't worry. To sum up, Redux does not represent changes in your application's state by modifying objects ( as you would with object-oriented paradigms). Instead state changes are represented as the difference between the input object and the output object (var output = reducer(input)). If you mutate either input or output you invalidate the state.
To sum up another way, immutability is a requirement of Redux because Redux represents your application state as "frozen object snapshots". With these discrete snapshots, you can save your state, or reverse state, and generally have more "accounting" for all state changes.
State of your app is only changed by a category of pure functions called reducers. Reducers have 2 important properties:
They never mutate, returning newly built objects: This allows reasoning about input + output without side-effects
Their signature is always function name(state, action) {}, so it makes it easy to compose them:
Assume the state looks like this:
var theState = {
_2ndLevel: {
count: 0
}
}
We want to increment the count, so we make these reducers
const INCR_2ND_LEVEL_COUNT = 'incr2NdLevelCount';
function _2ndlevel (state, action) {
switch (action.type) {
case INCR_2ND_LEVEL_COUNT:
var newState = Objectd.assign({}, state);
newState.count++
return newState;
}
}
function topLevel (state, action) {
switch (action.type) {
case INCR_2ND_LEVEL_COUNT:
return Object.assign(
{},
{_2ndLevel: _2ndlevel(state._2ndlevel, action)}
);
}
}
Note the use of Object.assign({}, ...) to create an entirely new objects in each reducer:
Assuming we have wired up Redux to these reducers, then if we use Redux's event system to trigger a state change ...
dispatch({type: INCR_2ND_LEVEL_COUNT})
...Redux will call:
theNewState = topLevel(theState, action);
NOTE: action is from dispatch()
Now theNewState is an entirely new object.
Note: You can enforce immutability with a library (or new language features), or just be careful to not mutate anything :D
For a deeper look, I highly recommend you checkout this video by Dan Abramov (the creator). It should answer any lingering questions you have.
The following benefits of immutability are mentioned in Redux documentation:
Both Redux and React-Redux employ shallow equality checking. In particular:
Redux's combineReducers utility shallowly checks for reference changes caused by the reducers that it calls.
React-Redux's connect method generates components that shallowly check reference changes to the root state, and the return values from the mapStateToProps function to see if the wrapped components actually need to re-render. Such shallow checking requires immutability to function correctly.
Immutable data management ultimately makes data handling safer.
Time-travel debugging requires that reducers be pure functions with no side effects, so that you can correctly jump between different states.
The main reason Redux is using immutability is that it doesn't have to traverse an object tree to check for the changes in every key value. Instead, it will only check the object's reference is changed or not in order to update DOM on state change.
Greate article https://medium.cobeisfresh.com/how-redux-can-make-you-a-better-developer-30a094d5e3ec
Along with immutable data, pure functions are one of the core concepts
of functional programming
Based on the official docs :
There are several reasons why you must not mutate state in Redux:
It causes bugs, such as the UI not updating properly to show the latest values
It makes it harder to understand why and how the state has been updated
It makes it harder to write tests
It breaks the ability to use "time-travel debugging" correctly
It goes against the intended spirit and usage patterns for Redux
Reudx official docs

Categories

Resources