I have a Map where the keys are instances of Vue components, like:
const instanceA = new Vue({...});
const instanceB = new Vue({...});
const register = new Map();
function registerInstance(instance){
register.set(instance, []);
}
How can I hook a callback to the destruction of a Vue component? I would like that when the component is being destroyed I would remove it's reference from my Map register. Something like:
function registerInstance(instance){
register.set(instance, []);
instance.onDestroy(() => {
register.set(instance, null);
register.delete(instance);
});
}
That would do a proper cleanup to avoid memory leaks, but not sure how to add a callback to a Vue instance's destruction process...
I do not want to add this logic in each component's beforeDestroy or destroyed functions. I would like to add them from outside the component, just using its instance pointer...
I thought about overriding instance.beforeDestroyed with a function that calls my code and then the original instance.beforeDestroyed from the instance. But that feels very wrong
OK, looks like we can use instance.$once("hook:beforeDestroy", () => { to add callbacks to hooks!
A example would be:
const instance = new Vue({});
instance.$once("hook:beforeDestroy", () => {
console.log('Destroying!');
});
setTimeout(() => {
instance.$destroy();
}, 1000);
<script src="https://vuejs.org/js/vue.min.js"></script>
Related
I'm working on a composable that's meant to encapsulate some logic with Vue3 and Vuex. The composable is working with a "feed" that is liable to change in the future, and comes from Vuex.
I'd like the composable to return the status of that feed when the feed changes as a computed value.
However, I'm unclear on how to fetch/wrap the value from Vuex so this computed property will change when the value in Vuex changes. For instance, at the top of the composable, I'm passing in the ID of the feed, fetching it from Vuex, and then using it in the composable like this:
const feed = store.getters['feeds/getFeedById'](feedId)
I'm then using the feed in a computed, inside of the composable, like this:
const feedIsReady = computed(() => feed.info.ready ? 'READY' : 'NOT READY')
However, when I change the feed object in Vuex via a mutation elsewhere in the application, the feed inside the composable does not change.
I've tried wrapping the feed in a reactive call and it's individual properties with toRefs but those approaches only provide reactivity within the composable itself, and don't capture changes from Vuex.
How would one wrap the feed from Vuex to provide reactivity? I need the changes in Vuex to propagate to my composable somehow.
Did you try to use vuex getter in your composable with computed property:
const feed = computed(() => store.getters['feeds/getFeedById'](feedId));
I see you are using store.getters['feeds/getFeedById'](feedId), which, AFAICT, means that the getter store.getters['feeds/getFeedById'] returns a function, and that the feedId is a parameter passed to the returned function.
If this is the case, this probably won't work, because that function likely doesn't return a reactive value.
I can't see the vuex code so don't know for sure, but assuming this is the case I would do something like this
const store = Vuex.createStore({
state() {
return {
feeds: {
1334:{info:{ready:false}},
}
}
},
getters: {
feeds(state) {
return state.feeds
}
},
mutations: {
change(state) {
state.feeds[1334].info.ready = !state.feeds[1334].info.ready;
}
}
});
function watchFeedState(feedid) {
const feeds = Vue.computed(() => store.getters.feeds)
const isReady = (v) => v && v.info && v.info.ready ? 'READY' : 'NOT READY';
const feedReady = Vue.ref(isReady(feeds[feedid]));
Vue.watch(store.getters.feeds, (v) => {
feedReady.value = isReady(v[feedid])
});
return feedReady
}
const app = Vue.createApp({
setup() {
const store = Vuex.useStore();
const isReady = watchFeedState(1334); // <= the feed from a higher order function
return {
isReady,
change: ()=>{store.commit('change')}
}
}
});
app.use(store);
app.mount("#app");
<script src="https://unpkg.com/vue#3.2.37/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vuex#4.0.0/dist/vuex.global.js"></script>
<div id="app">
<button #click="change">toggle state</button>
{{ isReady }}
</div>
This creates a higher order function to watch a specific feed id. This will watch the feeds getter for every change, but only update for a provided feedid.
So I've always thought of arrow functions to be a new better and version of normal js functions until today. I was following a tutorial on how to use firestore to store data when I came across a problem that made realise the two are different and work in a weird way.
His code looked like this:
//component
function Todos() {
const [ todo, setTodo ] = useState('');
const ref = firestore().collection('todos');
// ...
async function addTodo() {
await ref.add({ title: todo, complete: false});
setTodo('');
}
// ...
}
My code looked like this:
//component
const Todos = () => {
const ref = firestore().collection('todos');
const [todo, setTodo] = useState('');
const addTodo = async () => {
const res = await ref.add({ title: todos, complete: false });
setTodo('');
};
};
Now his version worked, while mine didn't.
After changing my code to look like his, it worked. But the weird thing i realised was this: after clicking on the button that invoked that function for the first time (with his function), i changed the code back to mine and it worked the second time. I did some reading on the two functions but i couldn't get to reasoning behind why this happened.
Arrow functions and normal function are not equivalent.
Here is the difference:
Arrow function do not have their own binding of this, so your this.setState refer to the YourClass.setState.
Using normal function, you need to bind it to the class to obtain Class's this reference. So when you call this.setState actually it refer to YourFunction.setState().
Sample Code
class FancyComponent extends Component {
handleChange(event) {
this.setState({ event }) // `this` is instance of handleChange
}
handleChange = (event) => {
this.setState({ event }) // `this` is instance of FancyComponent
}
}
With MobX, #computed properties are only cached when accessed from an observer or reactive context. So for example:
class Foo {
#computed
get somethingExpensive() { return someReallyExpensiveOperation() }
}
const f = new Foo();
setInterval(() => console.log(f.somethingExpensive), 1);
Will always call someReallyExpensiveOperation() because the computed is being called outside of a reactive context.
Is there a way to "enter" a reactive context to gain the benefits of #computed for setTimeout callbacks, EventEmitter event handlers etc?
EDIT: Another way to put this.. if I change the decorator to
class Foo {
#computed({ requiresReaction: true })
get somethingExpensive() { return someReallyExpensiveOperation() }
}
..it will throw when accessed from the setInterval example.
I use an instantly disposed autorun:
autorun((reaction) => {
console.log(f.somethingExpensive)
reaction.dispose()
})
Also made a npm module:
import runInReactiveContext from 'mobx-run-in-reactive-context'
// ...
runInReactiveContext(() => {
console.log(f.somethingExpensive)
})
Issue with a pattern i'm trying to use with redux.
I have a a mapDispatchToProps as below,
const mapDispatchToProps = (dispatch) => {
return {
presenter: new Presenter(dispatch),
};
};
and my presenter constructor looks as below:
constructor(dispatch) {
this.dispatcher = dispatch;
}
If I check the value of it in the constructor and after it's set, all is well. However later when a method tries to use it, the value of dispatch is undefined.
If i save it to a var outside the class, i.e.
let dispatch;
class Presenter {
constructor(dispatcher) {
dispatch = dispatcher.bind(this)
}
}
I've tried using .bind() within the first constructor also but it keeps becoming undefined!
Class methods were of the form:
someMethod() {
//do stuff
}
which means they have their own this scope bound... I'd have to bind the individual methods in the constructor, such as:
constructor(dispatch) {
this.dispatch = dispatch;
this.someMethod = this.someMethod.bind(this);
}
Or turn them into => functions so they take their context from the surrounding class, i.e.
someMethod = () => dispatch(/* an action */);
After reading official react.js documentation I understand how it should work in a good way, like
I have list of items in initial component state
adding new item through setState will update state and trigger update of UI
What should I do if I use external object as model like some global array which should be available for some not react.js parts of code OR could be modified with web sockets somewhere in future? Is calling ReactDOM.render after each action a good way? AFAIK it should work ok from performance point of view.
You still use setState:
let React = require('React');
let externalThing = require('tools/vendor/whoever/external-lib');
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = this.getInitialState();
}
getInitialState() {
// This assumes your external thing is written by someone who was
// smart enough to not allow direct manipulation (because JS has
// no way to monitor primitives for changes), and made sure
// to offer API functions that allow for event handling etc.
externalThing.registerChangeListener(() => this.updateBasedOnChanges(externalThing));
return { data: externalThing.data }
}
updateBasedOnChanges(externalThing) {
// note that setState does NOT automatically trigger render(),
// because React is smarter than that. It will only trigger
// render() if it sees that this new 'data' is different
// (either by being a different thing entirely, or having
// different content)
this.setState({
data: externalThing.data
});
}
render() {
// ...
}
}
If the external thing you're using is terribly written and you have to manipulate its data directly, your first step is to write an API for it so you don't directly manipulate that data.
let externalData = require('externaldata') // example: this is a shared array
let ExternalDataAPI = new ExternalDataAPI(externalData);
...
And then you make sure that API has all the update and event hooks:
class ExternalDataAPI {
constructor(data) {
this.data = data;
this.listeners = [];
}
addListener(fn) {
this.listeners.push(fn);
}
update(...) {
// do something with data
this.listeners.forEach(fn => fn());
}
...
}
Alternatively, there are frameworks that already do this for you (flux, etc) but they also somewhat dictate how many more things "should be done" so that might be overkill for your need.
Since your question is about organizing your code in a manageable way, I would first of all suggest pairing ReactJS with a Flux-type framework, like Redux or Relay.
If you want to skip that for now, then you can organize your project using some react components at the top of the hierarchy for storing and retrieving data. For example, in such a component, in its componentWillMount method, you can start a setTimeout that periodically checks your global array and calls setState when appropriate. The render method should then contain child components that receive this state as props.
Below is an example. Obviously, the timers can be replaced by whichever method you use to subscribe to your data changes.
// your global object
var globalState = {name: "Sherlock Holmes"}
function onData(callback) {
setInterval(function(){
callback(globalState)
}, 1500)
}
var Child = React.createClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
var Root = React.createClass({
getInitialState: function() {
return {}
},
componentWillMount: function() {
var that = this;
this.props.onData(function(data){
that.setState({external: data})
})
},
render: function() {
if (this.state.external)
return <Child name={this.state.external.name}/>
else
return <div>loading...</div>;
}
});
ReactDOM
.render(<Root onData={onData} />, document.getElementById('container'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>