Dealing with state in a realtime messaging app - javascript

I'm looking for some guidance with regards to managing state in a real-time messaging/chat app built with VueJS 2.
The app consists of several components which are outlined in the following diagram:
So far, I've implemented displaying (fake) conversations. The App component contains an array with conversation objects. For each child component, the relevant data is passed using props. This is really simple and works like a charm.
Now, I have to deal with actions/mutations from components deeply nested in the tree. For example, sending a message and appending it to the corresponding array of messages.
I figured it would be as easy as dispatching a (global) event in the AppConversationChatWindowInput component and handling it in the App component. Boy was I wrong. Apparently, this functionality was removed when Vue 2.0 was introduced in favor of Vuex. I'm not sure why it was removed, because in some situations this could be a perfectly reasonable way to deal with events.
I guess there are a couple of possible solutions:
Passing the websocket connection to each child component. This could technically work. The App would connect to the websocket server and pass this connection to its child components using props. When the user sends a message, it is echoed by the websocket server. The App component can listen for the message and append it to the array of messages.
Regardless of the technical feasability, this feels like a crappy and hard-to-maintain archicture to me. In my opinion, no component other than the App should be aware of the websocket connection, let alone its concrete implementation.
Manually bubbling up the event in each component in the chain.
This seems like a complete pain to maintain. Introduces a lot of needless complexity and failure points.
Using a global event bus.
This is possible, but why should an input field depend upon a global event bus? I don't like unnecessary dependencies and coupling. It adds complexity and makes things harder to test.
Using a global data store (Vuex).
See #3. Another dependency and added complexity. Also, if I would settle for Vuex, how would I retrieve data in my components? Do I pass it down using components (like I do now) or would a component deep down in the tree just grab it from the store directly? To me, it feels like the component knows a lot more than it should this way.
Any thoughts? What's the best way to handle state in my situation?

There's a bit of a disconnect between "I wanted to dispatch a global event" and "I don't want to use a global event bus." A global event bus is how you dispatch/broadcast a global event. It is, as you note, a good solution in some situations. It is not hard to set up when you need it, so there's no strong reason for it to be in core Vue.
You can create the bus as an instance property on Vue so it is available to every component:
Vue.prototype.$globalEventBus = new Vue();
Where you would have had vm.$dispatch(...) you would do vm.$globalEventBus.$emit(...) and the receiving component can set up vm.$globalEventBus.$on(...).
Alternatively, you could create a bus at the top level and pass it through the children as a prop. This avoids globals, and you don't have to worry about bubbling.
Finally, as I noted in my comment, native events bubble, and you can catch them at any component higher up the chain. You could catch the event(s) that send messages, or even roll your own events to catch.

Related

Can you bypass state management for simple objects? (global shared state)

Can you globally instantiate a class and that will be reliable on react-native? i.e.
// logs.ts
const instance = new Instance()
export { instance }
// something.tsx
const Component = () => {
instance.method()
...
}
If method were to increment a property by 1 would that property always have been incremented the number of times method was called throughout the project?
The primary benefit here is simplicity. For example, it's easier to define
class SomeClass {
constructor(properties){}
addProperty(key,value){ this.properties[key] = value }
}
than it is to do the equivalent in redux. Why don't people do the above more often?
Just changing the value on some object will not result in state changes/cause a component to re-render. You still need a state provider to have the state set to. If you want to change the state of that Provider you'll need to run setState (or dispatch and useReducer) which means you'll need to pass the dispatch of that function around to all of its children. As your app grows larger you'll definitely want to/need to useReducer and perhaps even several providers, you'll be re-implementing redux which is only about 200 lines of code anyway. So the question will become why did you re-implement redux which is such a popular library that most people know exactly how to use and there is plenty of documentation for, in favor of your homegrown version of redux which doesn't provide much additional value?
In redux a primary benefit is the tooling like redux-logger, redux-thunk, redux dev tools and time travel and others etc which allows you to replay the changes made or undo them. Sure it is easy to modify an object but using redux allows you to testably and consistently see how an object (the redux state) changes over time and undo them. You can test that action creators return the correct actions. You can separately test that given specific actions the reducer behaves as expected and replay that with mockStore. In short, using redux you get the enterprise version supported by a large community of experts who have helped improve and implement essentially the simple class that you demoed.
Although you can do this, it breaks away from the point of redux. Redux is meant to be a centralized state management store, and therefore, allow components to
access shared state from a single location and analyze shared states, because it comes from one place.
track history and actions taken to get state there
makes maintaining, debugging and collaborating on a codebase
much easier.
Doing the first option, all these benefits are lost.
That said, if you have multiple components in one file, and you want them all to share that same global state (not exporting it), using the class constructor to do so isn't a big deal, as long as the project isn't being worked on by other developers.
However, it would make more sense in that case to make an overall component class, and pass state down, as that is the point of React. Declaring a class outside and trying to manage state in the first way, would be a declarative approach, which is what React doesn't want developers to do (i.e there is a better way, like creating a hierarchy of components). That way, components re-render on a change in value, and no weird bugs arise

How to understand the effectscope in Vue?

The offical RFC
There is a example for effect
function createSharedComposable(composable) {
let subscribers = 0
let state, scope
const dispose = () => {
if (scope && --subscribers <= 0) {
scope.stop()
state = scope = null
}
}
return (...args) => {
subscribers++
if (!state) {
scope = effectScope(true)
state = scope.run(() => composable(...args))
}
onScopeDispose(dispose)
return state
}
}
I know what it will do, it will force all components to calculate only once when we use useMouse API
But I can't understand the concept of effect, and how does it work?
Espeically some APIs for effect like getCurrentScope. I tried to see the return values of getCurrentScope, but i have gained nothing.
Please help me!
effect is a common term used in reactive frameworks (both VueJS and React) to refer to (I believe) side effect. If you are familiar with functional programming, you probably already know that it is called side effect because it is not "pure function", because it mutates shared or global state.
Ignore the academic terminology, effect in these systems merely refers to any application defined method that does something bespoke, like
const foo = () => {
// I do something bespoke
}
The meaning of effect is really that broad. What your method actually does in its body does not matter to the framework. All that the framework knows is foo does some unstructured things. What VueJS does in extra, is to monitor, through its reactivity system, if your effect depends on any reactive data. And if it does, VueJS will re-run your effect every time the data it depends on changes.
effect (or side effect) isn't something bad or special or advanced. In fact, your application is all about making effects/side effects. For example, the commonest effect in a VueJS application is DOM manipulation. It is so common that VueJS extracts it into a different abstraction: template. Behind the scene, templates are compiled to render functions - which look a lot like the foo above - that get re-evaluated every time some dependent reactive data changes. That is how VueJS keeps your UI up to date.
Another extreme of common effects are those really bespoke ones, e.g. you just want to do some old fashion imperative things (like the jQuery style) whenever your data changes. And VueJS let you do it through watchEffect: you give VueJS a lambda, and VueJS will blindly call it every time its dependency changes without asking what it is doing.
VueJS discovers your dependency on reactive data by running your effect. As long as your effect accesses any reactive data (say, yourState.bar) during its execution, VueJS will notice that and record a dependency of your effect on yourState.bar
In its essence, the reactivity system is just the modern version of the good-old observable/subscriber pattern. Reactive states are the observables, and effects are the subscribers/observers. If you look beyond the magic layer and think of VueJS in the form of a subscriber pattern, there is one issue it cannot avoid: whenever you have subscribe, you will have to deal with unsubscribe, otherwise you will have memory or resource leaks simply because you keep holding on to subscribers (they in turn hold on to other things) and nothing can be freed. This unsubscribe part is what the RFC calls "effect dispose".
Typically you will have two challenges when dealing with this unsubscribing/disposing/cleaning up/cancelling business:
deciding when to unsubscribe/dispose
knowing how to unsubscribe/dispose
In a typical reactive framework, both of the above are application's responsibility. As the application dev, you are the only one who knows when a subscription is no longer needed and how to reverse the additional resource allocation (if any) you made at the time of creating the subscription.
But in a typical VueJS app you rarely need to manually deal with any kind of cleanup (stopping the DOM patching, watch, or computed etc). That is because VueJS takes care of it automatically. The reactive effects established within a component's setup method will be automatically disposed (whatever needed for a proper clean up) when the component is unmounted. How does that happen? Let's just say some other magic exists inside VueJS to associate all your effects with the life cycle of the corresponding component. Technically, as the RFC says, that magic is effectScope.
Conceptually, each component creates an effectScope. All your effects defined inside component setup method will be associated with that scope. When the component destroys, VueJS automatically destroys the scope, which will clean up the associated effects.
The RFC proposes to make effectScope a public api so that people can use it without using a VueJS component. This is possible because Vue3 is built with modularization. You can use Vue's reactivity module without using the entire VueJS. But without the underlying effectScope, you then have to manually dispose all your effects.
What would making a coffee look like in code?
snowingfox.getCupsOutOfCupboard();
snowingfox.getCoffeeOffShelf();
snowingfox.getMilkOutOfFridge();
snowingfox.boilingWater();
// ...
Now imagine each morning I wake up and have a coffee. You could say I'm making
a coffee in reaction to waking up. How would I run this code repeatedly in
response to an isMorning variable becoming true?
This is what effect solves in Vue 3. It wraps around a chunk of
code that should be executed in response to reactive data being changed. In practise you most likely won't use effect directly, and instead rely on
things like computed and watchEffect (which use effect in their
implementations).
In short: effect is one part of Vue's reactivity system and is Vue's way of
marking and locating code that should be re-run in response to data updates.
Docs: https://v3.vuejs.org/guide/reactivity.html
Course: https://www.vuemastery.com/courses/vue-3-reactivity/vue3-reactivity/
Here's how the initial code could be implemented to be reactive:
import { ref, watchEffect } from 'vue';
const isMorning = ref(false);
watchEffect(() => {
if (!isMorning.value) return;
snowingfox.getCupsOutOfCupboard();
snowingfox.getCoffeeOffShelf();
snowingfox.getMilkOutOfFridge();
snowingfox.boilingWater();
});

Is the solution for passing data up the chain in Vue.js really to chain event listeners/emitters?

The Vue API Docs/Guide talk about the pattern of parents passing props to children, who communicate back up to their parents via events. Additionally, the guide stresses that children absolutely should not mutate parent data, as that is the responsibility of the parent.
For a mostly-flat structure where you have one parent (e.g. 'App') and all components are children of that one parent, this works okay; any event only has to bubble once.
However, if you are composing a structure that is compartmentalized (e.g. App (holds state) -> ActiveComponent -> FormGroup -> Inputs...) then building interaction within the deeper parts (like the FormGroup/Inputs) requires making an event chain on each connection. Input will need to make an event v-on:keyup.enter="addInputValue"; then FormGroup will need to have a v-on:addInputValue="bubbleInputValue"; ActiveComponent will need a v-on:bubbleInputValue="bubbleInputValue"; and finally the App will need to respond to bubbleInputValue.
This seems really absurd that every link in the chain needs to know how to be responsible for handling the bubbling of an event that App wants to know about from the deepest branch of Input. The Vue guide suggests a State Store pattern to deal with this -- but that doesn't actually address the chaining (as you're simply adding a different root; the Store).
Is there an accepted convention/pattern for addressing the difficulty of communication that traverses a tall tree of components?
If you really want to keep state at app level, you can use non parent child communication
Which will be like following:
var bus = new Vue()
----------------------------
// in component A's method
bus.$emit('id-selected', 1)
----------------------------
// in component B's created hook
bus.$on('id-selected', function (id) {
// ...
})
But as stated in documentation:
In more complex cases, you should consider employing a dedicated state-management pattern.
Vuex to the rescue
As suggested in the comments, the general practice in the community for centralise state management is vuex, so all your state is separated from your component and view, you have actions which changes the store, which reactively changes your view. Flow look like this:

Flux store emitting changes to specific react components rather than all components.

Struggling to find or come up with an elegant answer to this one:
If I have multiple dynamic react components that are listening to one flux store to update their child components is it possible to emit changes to specific components rather than emitting changes to all the components that are registered to listen to changes on that store?
E.G: A dynamic component has a button and when clicked its tells the flux store to send some data to API. The dynamic component will it update its child view depending on the response and change emitted by the flux store. But since all the dynamic components are listening to the store they will all update their child views which is the undesired behaviour. Ideally the flux store could identify which component to emit the change to, or the components can identify that change is not for them.
Is this possible? Or does it go against flux principles?
I don't know if it violate flux architecture, but it seems not leveraging some beauties of it.
The beauty of a simple emit change (without change detail) is that a store wouldn't need to have explicit knowledge on views, also, with the React Virtual Dom framework, it shouldn't cost too much performance hit.
To further optimize the performance, you can implement shouldComponentUpdate on your React view (base on the differences in it's own properties), to avoid triggering the tree-diff algorithm.
See this: https://facebook.github.io/react/docs/component-specs.html
== Add more info ==
In more traditional MVC, the model will emit changes to a particular source and with particular details, e.g.
this.emit({
details: { x: 'x', y: 'y' },
source: objectA
)};
The view (or controller) that receive this needs such detail to update it's Dom, you will call the update(changes.details) instead of the initial render() method because Dom manipulation is expensive.
ReactJS 'solved' this by having another virtual Dom layer, which use pure Javascript to compute the 'optimal' differences in Dom manipulation, so in React, you never have a method call update(), you will always call render() base on current state of the view, and React does the optimization for you.
So using Flux with React, your store can just emit change without any details and the views that listen to it can just render with 'optimal' Dom manipulation (so if it's state hasn't been changed, there will be no Dom manipulation).
But of course, you will say in this case React will still trigger the virtual Dom diff computation, which still cost something. So to further optimize it, you can implement shouldComponentUpdate on a view that contains big sub-tree (base on it's own state), to avoid React to run the diff computation.
The beauty of emit a simple change, besides easier code, is that Store can be pretty much decoupled from view.
For example if you trigger specific change details for particular views, then you will need to remove or change code in store(s) when the view is not listening the that store anymore.
It does not go against flux principle but beware not having only one big store, sometime it's better to split in several tiny store.
But I think I understand your use case, one store containing a collection of similar objects (like a backbone collection).
So lets say your store receive a new object or an array of new object (or things to update in your store), you have a register function which will add this object (or update) to your store.
For sure this object has an id field (or something similar). Then for each new object of your array you just received you'll emit the id.
And your view are binded to their id as change event. Basically you use your store like an array, when the array is change you emit the key as event. Your view listen to this key/id and then get the specific data from your store still using this id/key.
Hope it's clear, let me know.

Custom events versus callbacks in ReactJS

I'm new to ReactJS and I'm trying to figure out how to store and manipulate global state. For example, I'm writing an editor app that has some global state: selected color/background, active tool, current selection, etc.
I'm thinking about having a root interface component to store this information, and I'm ok with explicitly passing the state around using properties. I guess the idiomatic way for a children component to change global state is calling a callback received from the parent - personally I find this a bit annoying.
Instead, I'm firing custom events at the children and setting listeners on the parent. So far it is working really well, but I looked at a lot of sample code and never saw people using this pattern.
Is there any practical consequences I should consider?
Flux is great for manipulating global state because the Stores are globally accessible. Globals are known to be bad, but the way you interact with the Stores is through Actions. Actions make the state predictable and localized. So even though the state storage itself is global, Actions make the state interactions very local. At work we tend to do both reading & writing through Actions, so every state change is an "event" that leads to easily programmable behavior.
I really wouldn't compare Flux to Angular as their principles and flows are entirely different. Suggest you try Reflux.

Categories