I'm making an SVG chart and I have a bunch of helper methods for converting between coordinates and dates. Currently I need to apply scaling everywhere and it's annoying, so I was considering adding the helper methods to the redux store, where the store has access to scaling and can automatically apply it in the methods.
Is this ideal?
I'm also considering creating a function that takes scale, and returns all of the helper methods with the scale curried in. If I do it this way, then I need to reinstantiate this curried function in every file I use it, passing scale each time.
Using redux store I'd only have to do it once.
EDIT: More detail
Restriction: "store" is inaccessible, outside of perhaps middleware.
convert
getDateFromX(x) / scale
to just
getDateFromX(x)
Where scale is built into the function. Like, getDateFromX is always divided by scale, so it should be in the function, but the scale is in the redux store.
I was originally asking if I could have my application reducer return a function in it's returned object "getDateFromX" that could be grabbed through mapStateToProps in connect. I understand it's frowned upon, or I wouldn't have asked the original question, I would have simply implemented this.
Also, there are about 7 more functions that do similar conversions. Converting between hours, days, date, and x.
No. You could technically do that, I guess, but it's definitely not a good use of Redux. (I'm actually having trouble envisioning how "methods attached to the store" would actually fit into things.)
The more idiomatic approach would be to use selector functions. For example:
import {createSelector} from "reselect";
const selectScale = state => state.scale;
const selectSomeValue = state => state.someValue;
const selectScaledValue = createSelector(
selectScale, selectSomeValue,
(scale, somevalue) => scale * someValue
);
If you consistently use selectScaledValue() in your mapStateToProps functions, then it would give you the scaled value every time either the scale or the original value changes.
As our app grows we have been running into similar issues. We try to keep our state as small as possible and then calculate additional information on demand. We've found this to be fairly robust but as the app grows we have to import these state helper functions throughout the app and it's not so user friendly. I've been toying with the idea of taking all the state helpers and attaching them to the state objects in a middleware piece so that components have easy access to them but they aren't actually being stored in our Redux store. I think you could combine markerikson's point but instead of duplicating these functions across your codebase pass them around with the state.
So instead of doing this all over your app
import { getDateFromX } from 'helpers'
...
getDateFromX(state)
You do this:
[ REDUX ] ---> [ CONNECT ] ---> [ UI ]
^
|
Attach helpers here
And then as your state is passed around you can do state.helpers.getDateFromX
This isn't fully fleshed out but I've also been trying to come up with an elegant solution to this issue that doesn't violate best practices of Redux.
Related
I heard that it's better to do testing this way but I don't understand why. What purpose does it serve to build a new state each time something changes rather than mutating what is already there? Is it faster?
No it's not faster. Mutating the state directly is usually faster.
Returning a new state makes reducers easier to test and predictable (because there is no side effect), also
we can prevent some unexpected behaviors from happening. For example If you are using PureComponent and you mutate state directly, you component may not update as you expected, because PureComponent use === to compare props.
Consider the code below, we are trying to render a list:
// current state
const list = ['foo', 'bar']
// we mutate the state directly
list[1] = 'hihi'
// in shouldComponentUpdate of a PureComponent
props.list === nextProps.list // true
In this case the component will not be aware of the update.
In addition to PureComponent, there are some optimizations in react-redux rely on this convention.
Take a read of the Redux docs on the issue, but basically it makes each update much more preditable, which makes everything from testing to rendering simpler.
The store has a method called getState that will return the current state of the store.
What prevents code somewhere in my application from (accidentally) modifying the returned state from store?
Let's say i call this:
let state = store.getState();
state.someProperty = 'fun';
The implementation that i've found on getState on the store object simply returns the inner state object that gets overwritten with each new action.
const getState = () => state;
In between actions/new states what prevents code from modifying the state that will be read by another subscriber? In my above example, setting someProperty to 'fun' will persist inside the store on the state property, until overwritten.
While i'm obviously not supposed to modify the state, a simple mistake might bind the state to some component that (unknowingly) modifies its inputs - perhaps on a 2-way binding in an angular environment?
<app-some-component [user]="state"></app-some-component>
Shouldn't getState() be implemented as a clone of its state model?
P.S. This is not specifically related to Angular - which is why i didn't add the tag - to allow more people not used to Angular to answer the question.
The answer is: nothing :)
The core Redux library itself technically doesn't actually care if state gets mutated or not. You could actually mutate in your reducers, or have other parts of your app get the state tree and mutate it, and the store itself wouldn't know or care.
However, mutation will break time-travel debugging, as well as make tests unreliable. Even more importantly, the React-Redux library assumes that you will handle your state immutably, and relies on shallow equality comparisons to see if the state has changed. (This is the reason why "Why isn't my component re-rendering?" is in the Redux FAQ. 99.9% of the time, it's due to accidental mutation.)
If you are concerned about mutation, you can use a library like Immutable.js instead of plain JS objects, or use one of the several tools for freezing your state in development to catch mutations.
I've created some factory functions that give me simple (or more advanced) reducers. For example (simple one - base on action type set RequestState constant as a value):
export const reduceRequestState = (requestTypes: RequestActionTypes) =>
(state: RequestState = RequestState.None, action: Action): RequestState => {
switch (action.type) {
case requestTypes.start:
return RequestState.Waiting;
case requestTypes.success:
return RequestState.Success;
case requestTypes.error:
return RequestState.Error;
case requestTypes.reset:
return RequestState.None;
default:
return state;
}
};
Using those factory functions and combineReducers from redux I can compose them into fully functional reducer that handles most of my casual actions. That gives me readable code and prevents me from making silly mistakes.
Factories are good for common actions but when I need to add some custom behavior (for action type) which should modify some part of the store significantly I would like to write a custom part of the reducer that will handle that action for me.
The idea is to compose reducers in an iterate manner, so combineReducers but for an array. This way I could use my factories creating reducer and then combine it with my custom reducer that handles some specific actions. The combineReducers for an array would then call the first one, recognize that nothing has changed and call the second (custom) one to handle the action.
I was looking for some solution and found redux-actions but do not quite like the way it links actions and reducers making the semantics little different from what I'm used to. Maybe I do not get it, but eventually I like to see that my reducer is written as pure function.
I am looking for some hint that will show me the way.
Is there any library or project that uses any kind of higher order reducers and combines them in some way?
Are there any downsides regarding composing reducers like described above?
Yep, since reducers are just functions, there's an infinite number of ways you can organize the logic, and composing multiple functions together is very encouraged.
The "reducers in an array" idea you're looking for is https://github.com/acdlite/reduce-reducers. I use it frequently in my own app for exactly that kind of behavior - running a combineReducers-generated reducer first, then running reducers for more specific behavior in turn.
I've written a section for the Redux docs called Structuring Reducers, which covers a number of topics related to reducer logic. That includes useful patterns beyond the usual combineReducers approach.
I also have a list of many other reducer-related utilities as part of my Redux addons catalog.
during my react-redux application development, I have run into a special use case:
Our application take some functions as resources (we dynamically load them, then use them as factory to create instances). Currently I put them in the store state and it works well. However, it seems kind of anti-pattern to put functions in Redux state, which will break the ability to persist and rehydrate the contents of a store.
So I try to move the functions out from the store state: I use a map to save functions and store the corresponding key in the state. The question is, by doing so, my state-to-ui transform no longer retains a pure function. As the functions are dynamicly loaded, content of the functions-map mutates. In different period (e.g. functions loading VS functions loaded), the same store state will result in different UI.
Now I feel that it's not so correct to put the functions in some place out of redux system. I need some advices about where to put these functions.
Storing functions in Redux state could be not a good idea exactly because of reasons you mentioned.
You can store in Redux state loading status of your functions. Something like:
{
'loadedFunctions': ['func1', 'func2']
}
or something more complex:
{
'functionsStatus': {
'func1': {
loaded: false,
loading: true
}
}
}
In that case same Redux state should result same UI (if body of your functions doesn't change)
I am kind of confused about which methods belong with and when to use them.
Right now, I am using subscribe for basically everything and it is not working for me when I want a quick static value out of Firebase. Can you explain when I should use subscribe vs other methods other than for a strict observable?
When working with async values you have a few options: promises, rxjs, callbacks. Every option has its own drawbacks.
When you want to retrieve a single async value it is tempting to use promises for their simplicity (.then(myVal => {})). But this does not give you access to things like timeouts/throttling/retry behaviour etc. Rx streams, even for single values do give you these options.
So my recommendation would be, even if you want to have a single value, to use Observables. There is no async option for 'a quick static value out of a remote database'.
If you do not want to use the .subscribe() method there are other options which let you activate your subscription like .toPromise() which might be easier for retrieving a single value using Rx.
const getMyObjPromise = $firebase.get(myObjId)
.timeout(5000)
.retry(3)
.toPromise()
getMyObjPromise.then(obj => console.log('got my object'));
My guess is, that you have a subscribe method that contains a bunch of logic like it was a ’.then’ and you save the result to some local variable.
First: try to avoid any logic inside the subscription-method -> use stream-operators before that and then subscribe just to retrieve the data.
You much more flexible with that and it is much easier to unit-test those single parts of your stream than to test a whole component in itself.
Second: try to avoid using a manual subscriptions at all - in angular controllers they are prone to cause memory leaks if not unsubscribed.
Use the async-pipe instead in your template and let angular manage the subscription itself.