"Private" Communication with React Flux dispatcher - reactjs

I'm attempting to decide on the best way to handle "private" communication between a store and a react component that can exist in multiple places. I've got two instances of the same component, both of which need to communicate with the same store. But, they need to behave independently. So when one gets an error message, the other should not display it.
The issue is, because my store and my dispatcher are both singletons, both instances of the component are getting the message. Both instances are listening to the store's events, so if that's a singleton, both will receive error events.
And, both instances are using the same dispatcher, so even if I had multiple instances of the store, all of them would be getting the same message at the same time, so they would all still react to it and emit a response.
I've been looking around but I haven't seen much about handling this problem, has anyone come across anything like it? I was thinking I could pass along a reference to the caller in the action, move it through to the event response and then check it again in the component, like so:
// my_component.js
import dispatcher from 'dispatcher';
import myStore from 'my_store';
const myComponent = React.createClass({
handleNewValue: function(){
dispatcher.dispatch({actionType:'check_error', value: 'value', caller: this});
}
componentWillMount: function(){
myStore.on('show_error', function(message){
if (message.caller === this){
doThing(message.error);
}
}
}
...
}
And:
// my_store.js
import dispatcher from 'dispatcher';
...
dispatcher.register(function(payload){
if (actionType === 'check_error'){
this.trigger('show_error', {error: 'is_bad', caller: payload.caller});
}
}
But for complex systems, this seems like it could get out of hand. It also seems more manual than I would like. Any thoughts would be appreciated!

Related

RxJS and repeated events

I am new to RxJs in general but am investigating a bug in some React code in which, upon an unrelated action, an old event seems to be emitted and rendered to a display error. Think if you had two buttons that generated two messages somewhere on screen, and clicking one button was showing the message for the other button.
Being new to RxJs I'm not positive where the problem lays. I don't see a single ReplaySubject in the code, only Obserables, Subjects, and BehaviourSubjects. So this is either misuse of an RxJs feature or just some bad logic somewhere.
Anyway I found the code with the related Observable and I'm not quite sure what this person was trying to accomplish here. I have read up on combineLatest, map, and pipe, but this looks like pointless code to me. Could it also be somehow re-emitting old events? I don't see dynamic subscriptions anywhere, especially in this case.
Tldr I don't understand the intent of this code.
export interface IFeedback {
id: number
text: string
}
export interface IFeedbackMessages {
message: IFeedback | undefined
}
feedback$ = new BehaviorSubject<IFeedback | undefined>(undefined)
feedbackNotifs$: Observable<IFeedbackMessages> = combineLatest([
feedback$
]).pipe(
map(([feedback]) => ({
feedback
})
))
I also found this which maybe be an issue. In the React component that displays this message, am I wrong but does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
const FeedbackDisplay: React.FC () => {
const [feedbackNotifications, setFeedbackNotifications] = React.useState<IFeedbackMessages>()
React.useEffect(() =>
{
const sub = notification$.subscribe(setFeedbackNotifications)
return () => sub?.unsubscribe()
}, [notifications$])
}
Could it also be somehow re-emitting old events?
Yes, it probably is. BehaviorSubject has the unique property of immediately emitting the last value pushed to it as soon as you subscribe to it.
It's great when you want to model some persistent state value, and it's not good for events whose actual moment of occurrence is key. It sounds like the feedback messages you're working with fall into the second category, in which case Subject is probably a better choice.
does it look like each time this thing renders it subscribes and then unsubscribes to the above Subject?
Not exactly. useEffect accepts a callback, and within that callback you can optionally return a "cleanup" function. React will hang onto that function until the effect is triggered again, then it calls it to clean things up (which in this case consists of closing out the subscription) to make room for the next effect.
So in this case, the unsubscribe will only happen when the component is rendered with a new value for notifications$. Also worth pointing out that notifications$ will only change if it's either passed as a prop or created within the component function. If it's defined outside the function (imported from another file for example), you don't need to (and in fact should not) put it into useEffect's dependency array.

Meteor handle.ready() in render() not triggering rerender of component

I have the following code in my render method:
render() {
return (
<div>
{this.props.spatulaReady.ready() ? <p>{this.props.spatula.name}</p> : <p>loading spatula</p>}
</div>
)
}
Which according to my understanding, checks if the subscriptionhandle is ready (data is there) and displays it. If no data is available, it should display a simple loading message. However, when I first load the page this snippet is on, it get's stuck on the loading part. On a page reload the data (usually) displays fine.
If I check the spatulaReady.ready() when the page first loads and while the display is stuck on 'loading spatula', and the data that should be there, the handle reports as ready and the data is there like it is supposed to be. If I refresh the page it all displays fine as well. The problem is, this way of checking for data and rendering if it has arrived has worked fine for me in the past. Is it because the render method is not reactive? Because handle.ready() should be reactive.
What makes it even weirder is that it sometimes DOES correctly display the data on page load, seemingly at random.
CreateContainer code:
export default createContainer(props => {
return {
user: Meteor.user(),
spatulaReady: Meteor.subscribe('spatula.byId', props.deviceId),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
Publication code:
Meteor.publish('spatula.byId', function(deviceId) {
const ERROR_MESSAGE = 'Error while obtaining spatula by id'
if (!this.userId) //Check for login
throw new Meteor.Error('Subscription denied!')
const spatula = SpatulaCollection.findOne({_id: deviceId})
if(!spatula) //No spatula by this id
throw new Meteor.Error(403, ERROR_MESSAGE)
if(spatula.ownedBy != this.userId) //Spatula does not belong to this user
throw new Meteor.Error(403, ERROR_MESSAGE)
return SpatulaCollection.find({_id: deviceId})
})
I know I'm missing a piece of the puzzle here, but I've been unsuccessful at finding it. If you don't know the solution to my specific problem, pointing me in the right direction with another way of waiting for the data to arrive before displaying it is also greatly appreciated.
EDIT: After doing some trial-and-error and reading various other posts somewhat related to my project, I figured out the solution:
export default createContainer(props => {
const sHandle= Meteor.subscribe('spatula.byId', props.deviceId)
return {
user: Meteor.user(),
spatulaReady: sHandle.ready(),
spatula: SpatulaCollection.findOne()
}
}, SpatulaConfig)
It still makes no sense to me that moving the ready() call to create container fixed all my problems though.
As you figured out, moving the .ready() call to createContainer fixes the problem. This is because Meteor reactivity only works when you call a reactive data source (a reactive function), such as collection.find() or subscriptionHandle.ready() within a reactive context, such as Tracker.autorun or createContainer. Functions within the React component, including render, are not reactive contexts from Meteor's perspective.
Note that React and Meteor reactivity are two different things. React's reactivity works simply so that whenever a component's props or state change, it's render function is re-run. It does not understand anything about Meteor's reactive data sources. Since createContainer (that is re-run by Meteor reactivity when reactive data sources in it change) simply passes props to the underlying component, the component is re-rendered by React when the props given from createContainer change.

Angular 2 setting a new value does not trigger an input change event

I'm running into a weird case that only seems to happen upon first loading a component on a heavily based component page (loading 30+ components).
#Component{
selector: <parent-component>
template: `<child-component [myObject]=myObject>
}
export class ParentComponent {
private myObject:DTOValue;
constructor(service:MyService){
service.getDTOValue().subscribe((dtoValue:DTOValue) => {
this.myObject = dtoValue;
});
}
}
#Component{
selector: <child-component>
template: `<div></div>
}
export class ChildComponent {
#Input set myObject(value:DTOValue) => {//do something};
constructor(){
}
}
In this code, the Parent is going to get a value to a child as an input. This value comes from a request at a later time, so when the child is first initialized, the input could be undefined. When the value does get returned from the request and is set on the variable myObject, I'd expect that the child component would receive an input event being triggered. However, due to the timing, it seems like this is not always the case, especially when I first load a page that contains a lot of files being loaded.
In the case that the child component doesn't receive the input, if I click else where on my page, it seems to now trigger the change detection and will get the input value.
The 2 possible solutions I can think of that would require some large code changes so I want to make sure I choose the right now before implement them.
Change the input to be an Subject, so that I push the input value which should ensure that a correct event is triggered(this seems like overkill).
Use the dynamic loader to load the component when the request as return with the correct value (also seems like overkill).
UPDATE:
Adding a plnker: http://plnkr.co/edit/1bUelmPFjwPDjUBDC4vb, you can see in here that the title seems to never get its data bindings applied.
Any suggestions would be appreciated.
Thanks!
If you can identify where the problem is and appropriate lifecycle hook where you could solve it, you can let Angular know using ChangeDetectorRef.
constructor(private _ref: ChangeDetectorRef)
method_where_changes_are_overlooked() {
do_something();
// tell angular to force change detection
this._ref.markForCheck();
}
I had a similar issue, only with router - it needed to do redirect when/if API server goes offline. I solved it by marking routerOnActivate() for check...
When you trigger change detection this way a "branch" of a component tree is marked for change detection, from this component to the app root. You can watch this talk by Victor Savkin about this subject...
Apologize, the issue ended up being my interaction with jQuery. When I triggered an event for a component to be loaded, inside of the jQuery code, it wouldn't trigger the life cycle. The fix was after the code was loaded to then call for a change detection.

Binding to event handler that calls setState in ComponentDidMount produces warning

I'm using jQuery to create event bindings in a ReactJS component's componentDidMount function, which seems like the right place to do this.
$('body').on('defaultSearchContext.registerQueryEditor', (function(_this) {
return function(event, component) {
_this.setState({
queryEditors: _this.state.queryEditors.concat([component])
});
};
})(this));
This code isn't actually run on componentDidMount, it's simply setting up the binding that later calls setState when the event fires. However, this generates the following warning every time this event triggers, which pollutes my console with dozens of warnings:
Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
I have tried moving the setState code to a separate function like onEvent and calling that from the binding in componentDidMount but the warning is still produced.
Ideally, I'd like to create the binding in the proper place, indeed, there is some issue with doing it in componentDidMount. If not, I'd like to know if it's possible to silence the warning, or whether I should perhaps file a bug for ReactJS itself. If it helps, I'm using ReactJS 0.14.3 (latest at this time).
This is similar to, but not the same as React Js onClick inside render. In that case, the solution is to return an anonymous function to onClick, but that doesn't seem applicable to my situation.
You are trying to coordinate events between independent components. This is a fairly standard thing to do, and it doesn't require DOM events. The standard practice for doing this in React is to use a store/dispatcher pattern like Redux or Flux (I personally prefer redux). However, if this is part of a larger, not-completely-React application, then this may not be possible. If it is just for a small piece of an React app, it may still be overkill.
All you need is an object to coordinate events. An event is just a collection of callbacks, possibly typed or keyed. This requires nothing more than an object shared between two places. DOM Events are overkill; jQuery is overkill. You just need to trigger a callback.
This is a VERY SIMPLE event coordinator.
let simpleEventCoordinator = {
callbacks: new Map(),
getHandler(eventKey) {
let handler = this.callbacks.get(eventKey);
if (!handler) {
handler = new Set();
this.callbacks.set(eventKey, handler);
}
return handler;
},
registerCallback(eventKey, callback) {
this.getHandler(eventKey).add(callback);
},
removeCallback(eventKey, callback) {
this.getHandler(eventKey).delete(callback);
},
trigger(eventKey, data) {
this.getHandler(eventKey).forEach(c => c(data));
}
Keep a map of callbacks, which will be nameOfEvent => callback(). Call them when asked. Pretty straightforward.
I know nothing about how your components are structured, but you said they are independent. Let's say they look like this:
React.render((
<div>
<QueryManager />
<button onClick={() => simpleEvent.trigger('event')}>{'Update'}</button>
</div>
), document.body);
This is all your component needs to handle this event
componentDidMount() {
simpleEvent.registerCallback('event', this.update);
}
componentWillUnmount() {
simpleEvent.removeCallback('event', this.update);
}
update() {
//do some stuff
}
I've put together a very simple codepen demonstrating this.
Looking at the source code of where that warning is coming from, it appears that if some reference is maintained before an update is about to happen, it throws that warning. So maybe the way your mixing the jQuery events and react is creating a memory leak? Its hard to say exactly because of the lack of surrounding code to your snippet what else could be going on.

React / Flux, setting multiple states

I'm new to react and I am using states for my data models.
I have a menu that shows a user's profile picture through state. It's state because a user can change his profile picture.
I'd like the Menu to slide in from the left, initially hidden. Hence I'd like to add the Menu's open/close status as a state as well.
I'm using the standard Flux pattern.
Here is the relevant code:
Menu.js
_onChange:function(){
this.setState({
opened: MenuStore.getMenuState(),
profilePicUrl: MenuStore.getUserPic()
})
},
componentDidMount:function(){
MenuStore.addChangeListener(this._onChange)
}
MenuStore.js
MenuStore = assign({},EventEmitter.prototype,{
emitChange: function() {
this.emit(CHANGE_EVENT);
},
addChangeListener: function(callback) {
this.on(CHANGE_EVENT, callback);
},
...(rest of class not shown)
})
MenuStore.dispatchToken = Dispatcher.register(function(action) {
switch(action.type) {
case ActionTypes.RECEIVE_USER_DATA:
_userData = action.details;
MenuStore.emitChange();
break;
case ActionTypes.TOGGLE_MENU:
_opened = !_opened;
MenuStore.emitChange();
break;
default:
// do nothing
}
});
Nav.js
toggleMenu:function(){
Actions.toggleMenu(); //in my actions.js it dispatches TOGGLE_MENU
}
render:function(){
return <div onClick={this.toggleMenu}>My Nav button</div>
}
I guess what I find wierd, is that I am setting the state of the User's profile picture without it having changed. Is this the correct way of doing things? Or should I emit separate change events and hence use separate callbacks, so that I set the states separately?
A related question is whether React will care if I set the state of something that hasn't changed. I.e does the diffing algo ignore the user's profile pic since it hasn't changed and therefore has no effect on React? OR does the fact that I've set the state in react, implicitly tell React that 'something has changed'?
Which is the correct React / Flux pattern? Set all the states in one callback? or set all the states separately?
There are a few things I learnt working with React and Flux that, I hope, can improve your approach:
Ship the whole state with the emitted event
In your example, you are emitting an event, and then the component is asking for data:
_onChange:function(){
this.setState({
opened: MenuStore.getMenuState(),
profilePicUrl: MenuStore.getUserPic()
})
}
I would suggest you move to a different pattern, where the store sends the whole state, no matter the event, every time it emits an event:
case ActionTypes.TOGGLE_MENU:
_opened = !_opened;
MenuStore.emitChange({opened: _opened, /* [...] */});
break;
Then in your component:
_onChange:function(snapshot){
this.setState({
opened: snapshot.opened,
profilePicUrl: snapshot.profilePicUrl
})
}
In this way your store scales up, no matter the amount of data you want to keep in the store.
Consider using Immutable.js or shipped immutability helpers
A related question is whether React will care if I set the state of something that hasn't changed.
React will trigger a virtual re-render in the virtual DOM. Then, it will execute the diff algorithm. As nothing has changed, nothing will be re-rendered.
You can avoid this by overriding shouldComponentUpdate:
Invoked before rendering when new props or state are being received. This method is not called for the initial render or when forceUpdate is used.
Use this as an opportunity to return false when you're certain that the transition to the new props and state will not require a component update.
I would strongly suggest that you start using either the immutability helpers, or Immutable.js. In this way it becomes easier to manage the whole re-rendering process, as it becomes trivial to understand when something has really changed.
Also, bear in mind that React is extremely fast. Unless you have > 100 components listening for changes, sometimes it is better to have some wasted re-render cycle, instead of writing a convoluted shouldComponentUpdate.
Code readability vs performance
I guess what I find weird, is that I am setting the state of the User's profile picture without it having changed.
This is really a trade off. If you have ~100 components listening to the same store, some of them interested in just one event, others in another one, then it would be the case to have different events (you would continue to send anyway the whole snapshot). Otherwise, I would suggest to keep your code simple: just publish one event.
Flux comes in different flavors
There are a few libraries now that implement ideas taken from Flux. I would suggest you have a look at Reflux and, especially, Redux.

Resources