I am trying to create a simple counter application to learn more about the Flux architecture, but it hasn't really clicked yet. Part of it is related to the implementation of ReduceStore.
In the Flux docs a simple example is given containing this reduce function:
reduce(state: number, action: Object): number {
switch (action.type) {
case 'increment':
return state + 1;
case 'square':
return state * state;
default:
return state;
}
}
The function is documented as follows:
reduce(state: T, action: Object): T Reduces the current state, and an action to the new state of this store. All subclasses must implement this method. This method should be pure and have no side-effects.
In other words: The reduce function does not mutate state, but returns the result of on operation with it (in the example an addition or multiplication).
While I have understood the general concept, I wonder how to actually use such store. If I got that correctly, a store should hold a certain state (the Flux docs mention the playback time of a video as example). But the ReduceStore does not implement any state, it has to be passed as an argument to the reducer. That means the actual state needs to be stored elsewhere in my application – probably in the component rendering the counter (the state of a counter app) which seems wrong to me.
Is this assumption correct and this is fine in a Flux architecture or am I missing something regarding a ReduceStore – maybe a different use case or something?
Related
If different reducers are associated to the same action and are performing changes to the store, are they receiving the same version of it before any edit happens? Is there a priority to consider?
Example:
Lets assume you dispatch an action Update to edit an entity on server side, once data is successfully updated, an effect will dispatch an Update_Success action with an instance of the newly received version of entity as payload. A first classic and logical reducer will use it to update the store:
case ItemActionTypes.UPDATE_ITEM_SUCCESS: {
const item = action.payload;
return {
...adapter.upsertOne(item, state),
loaded: true,
loading: false
};
}
Now lets assume that in a different file, you have a different reducer associated to the same action and needs to compare the newly received entity against the old one in store:
case ItemActionTypes.UPDATE_ITEM_SUCCESS: {
const item = action.payload;
const oldItem = state.entities[item.id];
const changes = getAffectedProperties(item, oldItem);
// ...
}
The question is: Is there any chance oldItem is actually holding the newly received item instead of the old one as the first reducer may have already updated it? Is it a first come first serve case? or is there anything on its implementation that guarantees all reducers are performing changes to the the same version of the store and maybe merging those changes at a further step?
If different reducers are associated to the same action and are
performing changes to the store, are they receiving the same version
of it before any edit happens? Is there a priority to consider?
Reducers only have access to their piece of state. So you're not supposed to really care about that.
Also, when one action is dispatched, all the reducers are called in a synchronous way. Which means that if for the ACTION_A you're modifying your store from different places (/ different reducers), your selectors (or even the view), will only be refreshed once. When all the updates for that action are applied on the store.
I have a data in my redux state that has like ~100,000 json records (with each record having some 8 key value pairs).
I have a logic written on client side to refresh this data every 30 seconds (every 30 seconds I make a call to server to get what records to remove and what records to add).
I do this processing in my reducer function, for this I have written a method "mergeUpdates" that iterates through the state.data object identifies what to remove and what to insert.
I was using fromJs(state.data).toJs from immutable to clone the state.data and make an update (state.data is not immutable). But this cloning turned out to be very costly operation (takes around 2 seconds for 100,000 records) hence I removed the cloning and started modifying state.data itself that resulted in "Assignment to function parameter" lint error because I am modifying data that is being passed to a function.
initialState = {
data: {}
}
doSomething(state, change) {
// iterates on state and updates state
state = doSomethingElse();
return state;
}
mergeUpdates(state, change) {
// some logic
state = doSomething(state, change)
// some more logic
return state;
}
export default function (state=initialState, action) {
switch (action.type) {
case REFRESH: {
return mergeUpdates(state.data, action.data)
}
}
}
What is the best practice to handle such cases, should I assign state to a new reference in "mergeUpdates" and "doSomething" methods and return a new reference, or make my data inside state an immutable or something else?
For ex: is this considered a good practice?
doSomething(state, change) {
let newState = state;
// process change and return newState
return newState;
}
mergeUpdates(state, change) {
let newState = state;
// apply change on newState
newState = doSomething(newState, change);
// return newState
return newState;
}
You should avoid using Immutable.js's toJS() and fromJS() functions as much as possible. Per its author Lee Byron, this are the most expensive operations in the library.
I would advise finding a different approach. Some suggestions:
If your data is already in Immutable.js, use the other functions it provides to help update that data (such as Map.withMutations())
If that data isn't already in Immutable.js, or you want to avoid using it, you might want to look one of the many immutable update libraries available, some of which are specifically intended to help merge new values into existing data structures.
Redux itself does not force user to use plain object for state management. You can use immutable object to store data and the only thing you need to do is that re-inits brand new state by fromJS as you did above.
Beside, immutable must be used in entire app. Actually I suggest you use spread operator for data modifying if your data structure is not complicated instead of cumbersome Immutable. (for me, over 3 level down the tree is complicated).
Before reading:
This isnt a matter of non working code but a question on architecture. Also i am not currently using the ReactRedux library as im first trying to understand how the parts work on their own in this test app. Its as short as i could cut it but unfortunately still lengthy, please bear with me
Short Intro
I've got an array of Bottle models. Using pseudocode,a bottle is defined like so:
class Bottle{
//members
filledLiters
filledLitersCapacity
otherMember1
otherMember2
//functions
toPostableObject(){
//removes functions by running JSON.
var cloneObj = JSON.parse(JSON.stringify(this));
//removes all members we dont want to post
delete cloneObj["otherMember1"];
}
//other functions
}
I've also got a React component that displays all Bottle items.The component needs to store the previous state of all Bottle items as well ( its for animating, disregard this ).
Redux usage
There are complex operations i need to perform on some of the Bottle items using a helper class like so:
var updated_bottles = BottleHandler.performOperationsOnBottles(bottleIds)
mainStore.dispatch({type:"UPDATED_BOTTLES",updated_bottles:updated_bottles})
I dont want to update the store for every operation as i would like the store to be updated all together at the end in one go. Therefore my BottleReducer looks something like this :
var nextState = Object.assign({}, currentState);
nextState.bottles = action.updated_bottles
Where action.updated_bottles is the final state of bottles after having performed the operations.
The issue
Even though everything works, im suspicious that this is the "wrong mindset" for approaching my architecture. One of the reasons is that to avoid keeping the reference to the bottle objects and mutating the state as im performing the operations, i have to do this ugly thing:
var bottlesCloneArray = mainStore.getState().
bottleReducer.bottles.map(
a => {
var l = Object.assign({}, a);
Object.setPrototypeOf( l, Character.prototype );
return l
}
);
This is because i need a cloned array of objects that still retain their original functions ( meaning they're actual instance clones of the class )
If you can point out the flaw/flaws in my logic i'd be grateful.
P.S: The reason i need to keep "deep clones" of the class instances is so that i can keep the previous state of bottles in my React component for the reason of animating between the two states when an update in render happens.
When dealing with redux architecture it can be extremely useful to keep serialisation and immutability at the forefront of every decision, this can be difficult at first especially when you are very used to OOP
As the store's state is just a JS object it can be tempting to use it to keep track of JS instances of more complex model classes, but instead should be treated more like a DB, where you can serialise a representation of your model to and from it in an immutable manner.
Storing the data representations of your bottles in its most primitive form makes things like persistance to localStorage and rehydration of the store possible for more advanced applications that can then allow server side rendering and maybe offline use, but more importantly it makes it much more predictable and obvious what is happening and changing in your application.
Most redux apps i've seen (mine included) go down the functional route of doing away with model classes altogether and simply performing operations in the reducers directly upon the data - potentially using helpers along the way. A downside to this is that it makes for large complex reducers that lack some context.
However there is a middle ground that is perfectly reasonable if you prefer to have such helpers encapsulated into a Bottle class, but you need to think in terms of a case class, which can be created from and serialised back to the data form, and acts immutably if operated upon
Lets look at how this might work for your Bottle (typescript annotated to help show whats happening)
Bottle case class
interface IBottle {
name: string,
filledLitres: number
capacity: number
}
class Bottle implements IBottle {
// deserialisable
static fromJSON(json: IBottle): Bottle {
return new Bottle(json.name, json.filledLitres, json.capacity)
}
constructor(public readonly name: string,
public readonly filledLitres: number,
public readonly capacity: number) {}
// can still encapuslate computed properties so that is not needed to be done done manually in the views
get nameAndSize() {
return `${this.name}: ${this.capacity} Litres`
}
// note that operations are immutable, they return a new instance with the new state
fill(litres: number): Bottle {
return new Bottle(this.name, Math.min(this.filledLitres + litres, this.capacity), this.capacity)
}
drink(litres: number): Bottle {
return new Bottle(this.name, Math.max(this.filledLitres - litres, 0), this.capacity)
}
// serialisable
toJSON(): IBottle {
return {
name: this.name,
filledLitres: this.filledLitres,
capacity: this.capacity
}
}
// instances can be considered equal if properties are the same, as all are immutable
equals(bottle: Bottle): boolean {
return bottle.name === this.name &&
bottle.filledLitres === this.filledLitres &&
bottle.capacity === this.capacity
}
// cloning is easy as it is immutable
copy(): Bottle {
return new Bottle(this.name, this.filledLitres, this.capacity)
}
}
Store state
Notice it contains an array of the data representation rather than the class instance
interface IBottleStore {
bottles: Array<IBottle>
}
Bottles selector
Here we use a selector to extract data from the store and perform transformation into class instances that you can pass to your React component as a prop.
If using a lib like reselect this result will be memoized, so your instance references will remain the same until their underlying data in the store has changed.
This is important for optimising React using PureComponent, which only compares props by reference.
const bottlesSelector = (state: IBottleStore): Array<Bottle> => state.bottles.map(v => Bottle.fromJSON(v))
Bottles reducer
In your reducers you can use the Bottle class as a helper to perform operations, rather than doing everything right here in the reducer directly on the data itself
interface IDrinkAction {
type: 'drink'
name: string
litres: number
}
const bottlesReducer = (state: Array<IBottle>, action: IDrinkAction): Array<IBottle> => {
switch(action.type) {
case 'drink':
// immutably create an array of class instances from current state
return state.map(v => Bottle.fromJSON(v))
// find the correct bottle and drink from it (drink returns a new instance of Bottle so is immutable)
.map((b: Bottle): Bottle => b.name === action.name ? b.drink(action.litres) : b)
// serialise back to date form to put back in the store
.map((b: Bottle): IBottle => b.toJSON())
default:
return state
}
}
While this drink/fill example is fairly simplistic, and could be just as easily done in as many lines directly on the data in the reducer, it illustrate's that using case class's to represent the data in more real world terms can still be done, and can make it easier to understand and keep code more organised than having a giant reducer and manually computing properties in views, and as a bonus the Bottle class is also easily testable.
By acting immutably throughout, if designed correctly your React class's previous state will continue to hold a reference to your previous bottles (in their own previous state), so there is no need to somehow track that yourself for doing animations etc
If Bottle class is a react component (or inside a react component) I think you could play with componentWillUpdate(nextProps, nextState) so you can check the previous state (do not unmount your component of course).
https://reactjs.org/docs/react-component.html#componentwillupdate
Deep cloning your class doesn't seem a good idea to me.
Edit:
"I've also got a React component that displays all Bottle items."
That's where you should keep and look for your previous state. Keep all your bottle in a bottles store. And get it in your components when you need to display bottles.
Inside componentWillUpdate you can check you this.state (which is your state just before being updated, ie your previous state) and nextState passed as a parameter which is the current state
Edit2:
why would you keep an complete class in your state ? Just keep data in state. I mean just keep an object that will be updated by your reducer. If you need to have some utils functions (parser...) do not keep them in your state, treat your data in reducers before updating your state or keep your utils/parser functions in some utils file
Also your state should stay immutable. So it means you reducer should return a copy of the updated state anyway.
I've got an array of Bottle models.
I think It makes more sense to have a model of BottleCollection.
Or maybe you have one Bottle model and multiple usages of it?
class Bottle{
//members
filledLiters
filledLitersCapacity
otherMember1
otherMember2
//functions
toPostableObject(){}
}
Hm, it looks like your model represents multiple things:
a cache of persistent data (retrieved via AJAX?)
data object (dumb fields)
a temporary state for user input (data to be POSTed?)
I wouldn't call it a model. It's 3 things: API wrapper/cache, data and pending changes.
I would call it REST API wrapper, data object and application state.
There are complex operations i need to perform on some of the Bottle items using a helper class like so:
var updated_bottles =
BottleHandler.performOperationsOnBottles(bottleIds)
It looks to be the domain logic. I wouldn't place the core logic of the application under the name "helper class". I would call it "the model" or "business rules".
mainStore.dispatch({type:"UPDATED_BOTTLES", updated_bottles:updated_bottles})
That looks to be a change in application state. But I don't see the reason for it. I.e. who requested this change and why?
I dont want to update the store for every operation as i would like the store to be updated all together at the end in one go.
That's a good reasoning.
So you'll have a single action type:
mainStore.dispatch({type:"UPDATED_DATA", { updated_bottles })
However, in this case you might need to clean up old state like this:
mainStore.dispatch({type:"UPDATED_DATA", { updated_bottles: null })
The reason i need to keep "deep clones" of the class instances is so that i can keep the previous state of bottles
I think the reason is that you keep REST API cache and pending changes in a single object. If you keep cache and pending changes in separate objects you don't need clones.
Another thing to note is that your state should be a plain JavaScript object, not an instance of a class. There's no reason to keep references to functions (instance methods) in a state if you know which type of data your state contains. You can just use temporary class instances:
const newBottlesState = new BottleCollection(state.bottlesCache, state.bottlesUserChanges).performOperationsOnBottles()
The details of my problem involve me integrating a D3 force graph with my redux state. Each tick update dispatches an action to update a position collection in my redux state. These positions are then merged with node data and use to update my visualization. React handles the DOM and D3 is used basically just to calculate forces, collision detection, etc.
I'm finding it difficult/impossible to maintain a smooth experience for the user using this design pattern. Locally on my laptop, I'm getting ~117ms between actions, well below 60fps (16ms between actions).
I've tried to simplify and streamline my middleware as much as possible to reduce latency.
What other strategies can I employ to get better update time using redux? Or am I trying to do something Redux was never meant to do?
On each tick means requestAnimationFrame right? If not, use it. :)
If you are only dispatching one action, meaning one Redux update -> one change callback -> one React re-render, there's not much you can do, your calculations take too much or your React component tree is too big and non efficient (no shouldComponentUpdate and so on).
In case you're dispatching more than one action on each frame, you may find useful one technique I've used in the past, which is wrapping my reducer so it can handle an array of actions at once. That way you can avoid re-renders until the last one. The code is surprisingly simple, as everything with Redux is:
function withBatching(originalReducer){
return function(state, action){
if(action.type === 'BATCH' && Array.isArray(action.payload)){
return action.payload.reduce(state, originalReducer)
} else {
return originalReducer(state, action)
}
}
}
//now wrap any (or all) of your reducers
const batchedAppReducer = withBatching(myCombinedAppReducer)
In your action creator, instead of dispatching N actions, dispatch one batch of them:
{
type: 'BATCH'
payload: [
{ type: 'MY_ACTION_TYPE', payload: 'xxxx' },
{ type: 'ANOTHER_THING', payload: 'xxxx' }
]
}
If you want to batch actions handled by different reducers, I would add batching on your combined reducer, just prior to createStore. Otherwise you can simply enable batching for your specific need.
I've used this technique when prototyping some games with great success: in my case I had to update multiple entities at once but didn't want to render until the "world" state was updated, so I batched all that updates.
Hope it helps, if you provide some details on your "update" cycle logic I'll try to help you further.
If you are using react-redux, there is also a batch API: https://react-redux.js.org/api/batch
From the doc:
import { batch } from 'react-redux'
function myThunk() {
return (dispatch, getState) => {
// should only result in one combined re-render, not two
batch(() => {
dispatch(increment())
dispatch(increment())
})
}
}
Redux documentations says I should make actions and action creators, like this:
function addTodo(filter) {
return {
type: SET_VISIBILITY_FILTER,
filter
}
}
Then write reducers, like this:
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
});
}
}
I then invoke the action using dispatch:
store.dispatch(addTodo("Ask question on stackoverflow"));
It seems there's a one-one correspondence between actions and reducers; the sole purpose of the action is to select a reducer and provide input data for that reducer.
Why don't we skip the middle man and identify actions with reducers and action creators with functions producing reducers? Then dispatch would take a single argument, a reducer/action of type State => State:
// Action/reducer. (Parametrised state transformer, really.)
const addTodo = text => state => {
return Object.assign({}, state, {
visibilityFilter: action.filter
});
}
// Dispatch takes as a argument the action/reducer
store.dispatch(addTodo("Ask question on stackoverflow"));
You'd lose the ability to serialise actions, but otherwise, it seems you'd get rid of boilerplate action creators and express more clearly the connection between actions and reducers. If you're in Typescript, you also get typechecking of the data in actions, which is difficult to express otherwise.
So what reasons for having actions as data am I missing?
The main purpose of action in Redux is to reduce the state.
Reduce method will be called on array of actions (thats why it called a reducer). Example:
import reducer from './reducer';
const actions = [
{type: 'INIT'},
{type: 'SOME_ACTION', params: {...}},
{type: 'RECEIVE_DATA', data: [...]},
{type: 'SOME_ANOTHER_ACTION', params: {...}},
{type: 'RECEIVE_DATA', data: [...]},
...
];
const finalState = actions.reduce(reducer, undefined);
Action creators is a function that can create actions. It is not necessary that action creator will create only one action.
Actually, if your reducer is able to receive functions instead of objects - your actions will be functions and it will do the main purpose, but you can loose some benefits of Redux functional abilities.
In that case the reducer will be implemented like this:
function reducer(state, action) {
return action(state);
}
The reasons why you may create actions as {type: 'ACTION_NAME'} format:
Redux DevTools expects this format.
You need to store sequence of actions.
Reducer makes state transformations on worker.
Everybody in Redux ecosystem use this format. It's a kind of convention.
Hot reloading abilities (your stored functions will not be reloaded).
You need to send actions as is on server.
Debugging benefits - to see the stack of actions with actions names.
Writing unit tests for reducer: assert.equal(finalState, expectedState).
More declarative code - action name and parameters are about "what to do" and not about "how to do" (but addTodo('Ask question') is declarative too).
Note about coupling between action creators and state changes
Just compare two notations:
First:
function someActionCreator() {
return {
type: "ADD_TODO",
text: "Ask question on stackoverflow"
}; // returns object
}
Second:
function someActionCreator() {
return addTodo("Ask question on stackoverflow"); // returns function
}
"In both cases we see that code is declarative and action creator is decoupled from state change. You can still reuse addTodo or dispatch two addTodo's or use middleware or dispatch compose(addTodo('One'), addTodo('Two')). The main difference is that we created Object and Function and place in code where state changes.
There is NOT a one-to-one mapping between actions and reducers. Per Dan Abramov's comments at https://github.com/Pitzcarraldo/reduxible/issues/8 :
It reinforces a very common misconception about Redux: namely that action creators and reducers are one-to-one mapping.
This is only true in trivial examples, but it is extremely limiting in real applications. Beginners exposed to this pattern couple reducers and action creators, and fail to realize they're meant to be many-to-many and decoupled.
Many reducers may handle one action. One reducer may handle many actions. Putting them together negates many benefits of how Flux and Redux application scale. This leads to code bloat and unnecessary coupling. You lose the flexibility of reacting to the same action from different places, and your action creators start to act like “setters”, coupled to a specific state shape, thus coupling the components to it as well.
As for actions and the "type" parameter, the other answers are right. That's deliberately how Redux was designed, and that was intended to give the benefits of serialization for debugging purposes.
Good question.
Separating actions from state changes is really a Flux pattern, rather than a specifically Redux thing. (Though I will answer the question with reference to Redux.) It's an example of loose coupling.
In a simple app, tight coupling between actions and state changes might be fine. But in a larger app, this could be a headache. For instance, your addTodo action might trigger changes in several parts of the state. Splitting actions from state changes - the latter performed in reducers - allows you to write smaller functions, which are easier to reason about and more testable.
Additionally, decoupling your actions and state changes allows your reducer logic to be more reusable. e.g. Action X might trigger state changes A and B, whilst action Y only triggers state change A.
Furthermore, this decoupling gives rise to a Redux feature called middleware. Middleware listens to action dispatches. It doesn't change the state of the app, but it can read the current state and the next state, as well as the action information. Middleware is useful for functionality extraneous from the core application logic, e.g. logging and tracking (dev tools were mentioned in a previous answer).
[UPDATE] But why have actions as objects?
If it were simply a matter of functions calling other functions, that decoupling would be much less explicit. Indeed it may even get lost; since most actions only enact one state change, developers might tire of a single function calling a single function and do away with the separation entirely.
The other thing is the Flux data flow model. One-way data flow is very important to the Flux/React paradigm. A typical Redux/React goes something like this: Store state -> Higher order React components -> Lower order React components -> DOM. The action is the aberration in the model; it's the backwards arrow transmitting data from the view to the store. It makes sense to make the action something as loud and emphatic as possible. Not a mere function call, but a dispatched object. It's as if your app were announcing Hey! Something important happened here!