Not all components see the same state sequence? - redux

See the example code:
https://codesandbox.io/s/G5A04RB23
State is simple, contains only a single value. Both Sub and App is connected to redux. When click the dispatch button, value incremented. And App will clear the state in componentWillReceiveProps when the value reaches 3.
But from the console log, it appears Sub never sees the 3 state.
VM220:80 action
VM220:21 reducer
VM220:83 dispatch: 0.525146484375ms
VM220:52 app prop 1
VM220:58 app render 1
VM220:32 sub prop 1
VM220:35 sub render 1
VM220:80 action
VM220:21 reducer
VM220:83 dispatch: 0.485107421875ms
VM220:52 app prop 2
VM220:58 app render 2
VM220:32 sub prop 2
VM220:35 sub render 2
VM220:80 action
VM220:21 reducer
VM220:83 dispatch: 0.4931640625ms
VM220:52 app prop 3 <<< app see 3
VM220:58 app render 3
VM220:32 sub prop 0 <<< sub never see 3
VM220:35 sub render 0
VM220:52 app prop 0
VM220:58 app render 0
I thought with redux, state should be consistent to all the components and every component should see the same state all the time. Is this the expected behavior or what am I doing wrong here?
EDIT1
Also tried with raw redux (instead of react-redux): https://codesandbox.io/s/Jq6gBoGzK
This time we do see both app and sub see the state value=3
So it appears to be a react-redux behavior. But it's a bit surprising behavior to me, I still expect all the components see every state change (thus props change). See my graph below:
Expect:
state1 -> (update props of comp1 ... compN) -> state2
Actual:
state1 -> (update props of comp1) -> state2 -> (update props of comp2)
Any reason why react-redux doing this instead of my expecting?
sub listener 1
app listener 1
sub listener 2
app listener 2
sub listener 3
app listener 3
sub listener 0
app listener 0

Here's what's happening behind the scenes when you're rendering your app:
Get props from Redux for the App component
Call App::componentWillReceiveProps() with props from step #1
Call App::render() with props from step #1
App::render() tells React to render the Sub component
Get props from Redux for the Sub component
Call Sub::componentWillReceiveProps() with props from step #5
Call Sub::render() with props from step #5
When you dispatch the clearAction() event in App::componetWillReceiveProps() (between steps #2 and #3), Redux immediately changes the value property in the store from 3 to 0, so by the time you get to step #5, the Redux store contains value=0, which is why it appears that the Sub component never sees the value=3.
You can see this by tweaking your code and calling store.dispatch(clearAction()); after a timeout:
if (nextProps.value == 3) {
setTimeout(function() {
store.dispatch(clearAction());
}, 1000); // dispatch the clearAction() after one second
}
With that change you should see the value=3 render all the way down to the Sub component, and then automatically change to value=0 one second after your click when the clearAction() event gets dispatched.

Related

Providing a source of reactive data

Context: My current Meteor-React project is a teaching app where a teacher can observe remotely what the learner is doing. There are many different views that the learner can use, so I need to separate the data-sharing aspect from the views themselves. The same views will be displayed on the teacher's device, with the display controlled by the student's actions.
Questions:
* Is the technique I am using sound?
* How do I prevent a component from being re-rendered when its input has not changed?
Details: I have created a bare-bones prototype (see below). This uses a Source instance (which in the app itself will be updated through a MongoDB collection) to provide reactive data for a view component. In my prototype, I simply generate random data.
I have had two surprises.
One: I discover that if I call .get() on a ReactiveVar in the source, this is enough to trigger the Tracker object to read in new values even if I return the value of a completely non-reactive variable. As can be expected, if the value of the ReactiveVar does not change, then the Tracker ignores any changes to the non-reactive variable.
Two: The value obtained by the Tracker is forwarded to the componentsprops` causing a re-render even if the value is unchanged.
Code:
import React, { Component } from 'react'
import { ReactiveVar } from 'meteor/reactive-var'
import { withTracker } from 'meteor/react-meteor-data'
/// SOURCE ——————————————————————————————————————————————————————————
class Source {
constructor() {
this.updateData = this.updateData.bind(this)
this.updateData()
}
updateData() {
const reactive = Math.floor(Math.random() * 1.25) // 4 times as many 0s as 1s
data.set(reactive)
console.log("reactive:", reactive)
this.usable = ["a", "b", "c"][Math.floor(Math.random() * 3)]
console.log("usable: ", this.usable)
setTimeout(this.updateData, 1000)
}
get() {
data.get() // We MUST get a reactive value to trigger Tracker...
return this.usable // ... but we CAN return a non-reactive value
}
}
let data = new ReactiveVar(0)
const source = new Source()
/// COMPONENT ———————————————————————————————————————————————————————
class Test extends Component{
render() {
console.log("rendered:", this.props.data)
return (
<div>
{this.props.data}
</div>
)
}
}
export default withTracker(() => {
const data = source.get()
console.log("UPDATE: ", data)
console.log("")
const props = {
data
}
return props
})(Test)
Sample Console output, with annotations:
reactive: 1
usable: b
UPDATE: b
rendered: b <<< initial value rendered
reactive: 1 <<< no change to reactive value...
usable: a <<< ...so usable value is ignored
reactive: 0 <<< reactive value changes...
usable: c <<< ... so an update is sent to the component
UPDATE: c
rendered: c <<< c rendered
reactive: 0 <<< no change to the reactive value...
usable: c
reactive: 0
usable: b
reactive: 0
usable: c
reactive: 0
usable: b
reactive: 1 <<< but when reactive value changes
usable: c <<< the usable value does not
UPDATE: c
rendered: c <<< c re-rendered, although unchanged
To recap: My plan is to increment a ReactiveVar in my Source instance each time a new datum arrives from the student. However, if the student is simply moving the cursor, then I want only the component that displays the student's cursor to re-render, and not the entire view.
I would appreciate any insights into how I can achieve this elegantly.
The behaviour you are seeing is part of the Meteor 'magic' - it sees that your reactive variable depends on a plain variable, and makes that reactive too, or more correctly it sets up a watch on it.
withTracker will often trigger multiple renders, so the best way to optimise these is use of React.memo()
I don't personally like React.memo, it feels clumsy, and makes the developer do work that feels unnecessary. There is a good article here which explains it:
https://dmitripavlutin.com/use-react-memo-wisely/
When a component is wrapped in React.memo(), React renders the
component and memoizes the result. Before the next render, if the new
props are the same, React reuses the memoized result skipping the next
rendering.

Aframe next previous function

Is anyone aware of a next/previous component that works inside WebVR with Aframe, so that if I am looking at an entity with id entity1 at position 0 0 -3, then I click next and I am looking at an entity with id entity2 at position 0 0 -3 replacing the previous entity, the previous entity returns position y -3 and scale 0 0 0, I click next again and now I am looking at entity with id entity3 which is now at position 0 0 3
I have been exploring how to build a next button starting with either an array or a javascript switch so that each time I press click, (listening for clicks on the next button) it brings me another new entity, so I might click next 6 times and see 6 different entities at position 0 0 -3, with each new entity replacing the previous entity, similarly a back button would do the same process but in reverse order.
Here is the idea in code, I am trying to complete it, but I’m getting lost in the forest for the trees so to speak.
AFRAME.registerComponent("carousel", {
init: function() {
this.el.addEventListener("turn_carousel_left", (e) => {
document.querySelectorAll('.carousel_left').forEach(function(e){
e.emit('close_the_menu');
emit +1 carousel index
this.el.addEventListener("turn_carousel_right", (e) => {
listen right events
emit -1 carousel index
if carousel index = 1 then emit position_named_1
selected_carsouel_item = carousel_index_list[carouselindex]
http://carousel-b.glitch.me

Redux - dependant application state

I am currently trying to wrap my brain around redux and I currently have a problem understanding, how to handle dependent state in redux.
As an example think of a spreadsheet application:
In Cell A1 and A2 the user is entering values.
Cell A3 now has the following dependent state (=Sum(A1;A2))
So now, when the user enters "2" into A1 => we send an "UpdateCellAction: A1=2"
and then, he enters "4" into A2 => we send an "UpdateCellAction: A2=4"
But because of these changes, my formula in A3 has to react and also modify the state object by viewing the sum of 2 and 4, which is 6
How is something like this done in Redux?
And, what if there was another Cell B22 (whatever) which calculated another value based on A3, A2 and A1? (that state would then depend on A1, A2 and A3)
This is a common use case. What you're looking for is the react/reselect library:
From the reselect github project page:
Selectors can compute derived data, allowing Redux to store the minimal possible state.
Selectors are efficient. A selector is not recomputed unless one of its arguments change.
Selectors are composable. They can be used as input to other selectors.
Your question isn't clear if you consider each cell to be an individual component or not.
If each cell is an individual component:
You would use Redux to dispatch an action every time each cell is edited and keep that value under a unique key in the Redux store. You would also use mapStateToProps to get all the values of the other components you want to calculate locally.
...
/**
* Redux mappings
*/
const mapStateToProps = (state) => {
return {
cellA1: state.cells.cellA1,
cellA2: state.cells.cellA2,
};
};
const mapDispatchToProps = (dispatch) => {
return {
updateCell: (newCellValue) => {
dispatch(Actions.updateCell(newCellValue));
},
};
};
const ConnectedCellComponent = connect(
mapStateToProps,
mapDispatchToProps,
)(CellComponent);
If the spreadsheet as a whole is a component:
You would store all the cell data in a local state object, then whenever a cell updates you would call setState() and render() would get called automatically and all the cells would recalculate appropriately.

part of the store relies on some other part

As a scenario, the user can click a button to create a list of timestamps that shows the corresponding times when the clicks are made. User can also click on an item on the list to remove an item.
In terms of the store, there's a counter state that keeps track of how many times the button has been clicked, and then there's another state that keeps track of a list of timestamps. And each item on list state has an id field that derive from the counter state. So one part of the store depends on another part.
As an attempt, I dispatch one action, and both reducers handle the same action, and it works fine, but only that it's not DRY. Before dispatching, I have to add 1 to the counter state in order to get the new id which I use as the action payload, after dispatching, I add 1 to the counter state again to return the new counter state. That's repeating myself.
What's the general standard way of handling a problem of this nature?
The general simple way is to use thunks. You need to setup a middleware, check out the docs:
https://github.com/gaearon/redux-thunk
This allows you to dispatch a function instead of a simple action. Within that function, you can access state and dispatch as many times as you want.
In your scenario, you would first increment the counter, then retrieve the length to get your new id, and then dispatch another action to create a timestamp.
Some imaginary code for your action creators:
// basic action creators to be handled in your reducers
function incrementCounter(){
return { type: 'INCREMENT'}
}
function createTimestamp(id){
return { type: 'CREATE_TS', id }
}
// this is the thunk
function incrementAndTimestamp(){
return (dispatch, getState) => {
// increment the counter
dispatch(incrementCounter())
// generate an "id" from the resulting state
const newId = getState().counter.length
// and use that id to further update your state
dispatch(createTimestamp(newId))
}
}
You will need to handle those 2 different actions in your reducers and you have now two separate pieces of code. The thunk is the glue that dispatches, gets the data from one part, and uses it to affect the other part.

async reducer with react-router seems to have multiple stores?

I have an async reducer structure. On any given page, I inject the page's reducer with:
export const injectReducer = (store, { key, reducer }) => {
// store.asyncReducers already initialized to {}
// makeRootReducer just returns combineReducers({...store.asyncReducers})
store.asyncReducers[key] = reducer
store.replaceReducer(makeRootReducer(store.asyncReducers))
}
I use react-router 3 and the plain routes definitions on any given page. I use require.ensure for async handling of routes within getComponent of the plain route definition:
export default (store) => ({
path : 'counter',
getComponent (nextState, cb) {
require.ensure([], (require) => {
const Counter = require('./containers/Counter').default
const reducer = require('./modules/counter').default
injectReducer(store, { key: 'counter', reducer })
cb(null, Counter)
}, 'Counter')
}
})
My problem is that if I use dispatch followed by browserHistory.push, I would expect that the state gets updated before going to the new page. What happens however, is that there appears to be 2 separate stores. For example, navigating between pages, the value of the counter from the previous page seems to be preserved despite it being on the same key. What is going on here???
Sample repo of my problem. You can git clone, npm install, npm start and go to localhost:3000/counter. If you click Double (Async) it doubles the counter and then goes to the /otherPage. If you then click Half (Async) it will bring you back to /counter. However the value of the counter is the value from doubling, not from halving. Also, importantly, pulling up Redux DevTools and navigating between the pages seems to show the counter change for ALL data before. Almost as if the entire store was replaced, yet the prior values are preserved.
What is going on here???
After much investigation I have discovered that in development ReduxDevTools will recompute the entire state history on a new page. Inserting a new reducer where initialState is different on the new page results in 2 different results. There are not 2 stores or any caches, it is just redux dev tools recomputing the entire state history with different initialStates.
Hence in my scenario, dispatch was updating the state on my first page. Then browserHistory makes a push to go to the second page. The second page recomputes the entire state history. However the second page has a reducer that is missing the action handler for the first page. Hence when recomputing the entire state history, the state doesn't change from that last action.

Resources