Maybe I have misunderstood what a getter is in Vuex, but say I have a getter that gets the size of a DOM element, a div for example. I would do something like this :
const getters = {
getContainerWidth (state) {
return document.getElementById('main-content').clientWidth;
}
}
Now when I start my app, all the getters seem to be run straight away. What if the div isn't available at startup? How do I rerun a getter?
I run the getter like this at the moment :
import store from '#/store'
store.getters['myModule/getContainerWidth']
I thought maybe this would work :
store.getters['myModule/getContainerWidth']()
But since store.getters is an object containing properties and values, and the values not being functions, I can't rerun them.
Any ideas?
Getters should depend on state field to be reactive. It you want to observe clientWidth changes - it does not work.
If you want to use it like function then just return function from getter:
const getters = {
getContainerWidth (state) {
return () => {
let container = document.getElementById('main-content');
return container ? container.clientWidth : 0
};
}
}
and use it like getContainerWidth()
Related
I have a Vue block that I need to bind to a boolean property:
<div class="row" v-if.sync="isThisAllowed">
To calculate that property I need to make an API call using Axios, which has to be asynchronous. I've written the necessary code to get the value:
public async checkAllowed(): Promise<boolean> {
var allowed = false;
await Axios.get(`/api/isItAllowed`).then((result) => {
var myObjects = <MyObject[]>result.data.results;
myObjects.forEach(function (object) {
if (object.isAllowed == true) {
hasValuedGia = true;
}
})
});
return allowed;
}
What I did then - I'm not very experienced with Vue - is to add a property to the Vue model and assign a value to it in created:
public isThisAllowed: boolean = false;
async created() {
this.checkAllowed().then(result => {
this.isThisAllowed = result;
});
}
This works in the sense that the value I'm expecting is assigned to the property. But Vue doesn't like it and complains
Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
Most of the values on the model are exposed via getters:
get isSomethingElseAllowed(): boolean {
return this.productCode === ProductTypes.AcmeWidgets;
}
But I need to "await" the value of the async function, which would mean making the getter async which then, of course, makes it a Promise and I can't bind that to my model?
What's the right way to go about this?
You can't define a property that way, instead define isThisAllowed in the data object
as
data: function(){
return {
isThisAllowed: false
}
}
And make checkAllowed into a normal function and set this.isThisAllowed = allowed inside it
I have gone through the definitions of the Pure and Impure Javascript functions in the ReactJs Official Docs.
Pure functions are ones that do not attempt to change their inputs, and always return the same result for the same inputs.
Example
function sum(a, b) {
return a + b;
}
Impure function is one that changes its own input.
Example
function withdraw(account, amount) {
account.total -= amount;
}
Now, can somebody tell me, how can I mistakenly make functions impure in React/Redux, where pure functions are required?
React and Redux both need pure functions coupled with immutability to run in a predictable fashion.
If you don't follow these two things, your app will have bugs, the most common being React/Redux not able to track changes and unable to re-render when your state/prop changes.
In terms of React, consider the following example:
let state = {
add: 0,
}
function render() {
//...
}
//pure function
function effects(state,action) {
//following immutability while updating state, not directly mutating the state.
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}
function shouldUpdate(s) {
if(s === state){
return false
}
return true
}
state = effects(state, 'addTen')if(shouldUpdate(state)) {
render();
}
The state is held by the state object which has only added property. This app renders the app property. It shouldn't always render the state when anything happens but should check whether a change occurred in the state object.
Like so, we have an effects function, a pure function which we use to affect our state. You see that it returns a new state when the state is to be changed and returns the same state when no modification is required.
We also have a shouldUpdate function which checks using the === operator whether the old state and the new state is the same.
To make mistakes in terms of React, you can actually do the following :
function effects(state,action) {
doRandom(); // effects should only be called for updating state.
// Doing any other stuff here would make effects impure.
if(action == 'addTen') {
return {...state, add: state.add + 10}
}
return state;
}
You can also make mistakes by setting the state directly and not using effects function.
function doMistake(newValue) {
this.state = newValue
}
The above should not be done and only effects function should be used to update the state.
In terms of React, we call effects as setState.
For Redux:
Redux's combineReducers utility checks for reference changes.
React-Redux's connect method generates components that check reference changes for both the root state and the return values from mapState functions to see if the wrapped component actually needs to re-render.
Time-travel debugging requires that reducer be pure functions with no side effects so that you can correctly jump between different states.
You can easily violate the above three by using impure functions as reducers.
Following is taken directly from redux docs:
It's called a reducer because it's the type of function you would pass to Array.prototype.reduce(reducer, ?initialValue).
It's very important that the reducer stays pure. Things you should never do inside a reducer:
Mutate its arguments;
Perform side effects like API calls and routing transitions;
Call non-pure functions, e.g. Date.now() or Math.random().
Given the same arguments, it should calculate the next state and return it. No surprises. No side effects. No API calls. No mutations. Just a calculation.
Simply said the state cannot be mutated. A new instance of the state should be returned every time there is a change so
This code is not correct :
const initialStates = {
items: ['item1']
}
export const ItemMaster = (state = initialStates, action) => {
switch (action.type) {
case TYPES.ADD_ITEM:
{
state.items.push(action.item)
return state
}
default:
return state
}
}
This code when written as a pure function below, This returns a new instance of the array it does not modify the actual array itself. This is the reason you should use a library like immer to handle immutability
const initialStates = {
items: ['item1']
}
export const ItemMaster = (state = initialStates, action) => {
switch (action.type) {
case TYPES.ADD_ITEM:
{
state = {...state,items:state.items.concat(action.item)}
return state
}
default:
return state
}
}
You could make pure functions impure by adding API calls or writing codes that result in side effects.
Pure functions should always be on point and self-explanatory, and should not require you to refer 3 or 4 other functions to understand what's going on.
// Pure Function
function USDtoEUR(USD, todayRate) {
return USD * todayRate;
}
// Impure Function
function USDtoEUR(USD) {
const todayRate = getTodayRate();
return USD * todayRate;
}
In case of React / Redux
const mapState = async state => {
const { data } = await whatDoINeed()
let mappedState = {}
if (data.needDolphin) {
mappedState.dolphin = state.dolphin
}
if (data.needShark) {
mappedState.shark= state.shark
}
return mappedState;
}
// Or for Redux Reducer
// Bad
{
setData: (state, payload) => {
const set = whatToSet()
return {
...state,
set.dolphin ? ...{ dolphin: payload.dolphin } : ...{},
set.shark ? ...{ shark : payload.shark } : ...{},
}
}
}
// Good
{
setData: (state, payload) => {
return {
...state,
// Just send only the things need
// to be sent
...payload
}
}
}
This should not be done. Everything a connect function or reducer function needs must be supplied through argument or written within its function. It should never get from outside.
For an Ember app, is it possible to use a constant as part of a computed property key ?
So, essentially, I have a constant as below;
MY_SECTION {
MY_FIELD: "my-field-id"
}
What I want is a computed property on "my-field-id" i.e.
myCP: function() {
console.log('Inside CP...');
}.property('section.field.my-field-id.value')
However, I want to be able to use constant for my-field-id instead of using it directly. Is that possible ?
Ola #testndtv, thanks for your question! Yes it is entirely possible to use a constant in the key for a computed property, but to make use of it you will need to use the more modern syntax that #jelhan was mentioning because .poperty() is deprecated.
Here is a working example of a controller that I have tested locally and is working as you would expect:
import Controller from '#ember/controller';
import { defineProperty, computed } from '#ember/object';
const PROPERTY_ID = 'some-random-string-that-is-too-long-to-write';
export default Controller.extend({
// this is just for the example so we can show the value in the template
// it is not needed to get this to work
PROPERTY_ID: PROPERTY_ID,
init() {
this._super(...arguments);
defineProperty(this, 'myCP', computed(PROPERTY_ID, function() {
return this.get(PROPERTY_ID);
}));
},
actions: {
addOne() {
// this is just for the example to stop the result always being NaN because
// null + 1 = NaN
let value = this.get(PROPERTY_ID) || 0;
this.set(PROPERTY_ID, value + 1);
}
}
});
As you can see we are making use of defineProperty which is being imported from '#ember/object'. You can read more about it in the API documentation
The key insight here is that you need to define the property dynamically in the init() for this Ember object.
The corresponding template for this Controller is as follows:
Property ID is: {{PROPERTY_ID}}
<br>
And the value is: {{get this PROPERTY_ID}}
<br>
<button {{action 'addOne'}}>Add One</button>
I'm trying to understand deep observability in MobX.
In particular, in the following code I'd like the autorun to be called every time I run setCommentCountForPost, but currently it isn't.
How should I fix this code? And, observable on a property of Post is enough to activate the autorun when I read the list in which the post is contained? (as I'm doing in the autorun)
I'm using MobX 5.
Edit: I discovered the code is working properly if I use the following call inside the autorun: console.log(toJS(p.getPosts()));.
This is interesting, but why, and how should I do if I only want to call getPosts()?
This is the code
import { useStrict, configure, autorun } from 'mobx';
import { toJS, observable, action, computed } from 'mobx';
configure({ enforceActions: true });
class Post {
#observable commentCount = 0;
setCommentCount(c) {
this.commentCount = c;
}
}
class PostList {
#observable _posts = {};
#action createPost(id) {
this._posts[id] = new Post();
}
#action setCommentCountForPost(id, c) {
this._posts[id].setCommentCount(c);
}
getPosts() {
return this._posts;
}
}
let p = new PostList();
p.createPost(1);
autorun(function test () {
console.log(p.getPosts());
});
p.setCommentCountForPost(1, 22);
MobX tracks property access, not value
in your example, the autorun function only tracking the _posts, but not the property of _posts, so if you change the _posts value the tracking function will worked
console.log(toJS(p.getPosts())) worked bacause of the toJS function in order to convert the observable value to normal value , it access the property of _posts.
if you hope the p.getPosts() worked, you should iteration access the property of _posts.
I would like to pass some properties from a parent to all of his children when those are transcluded (content distribution syntax). In this case, the parent doesen't know (as far as I know) his children, so I don't know how to proceed.
More specificly, I want a way to write this :
<my-parent prop1="foo" prop2="bar">
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
</my-parent>
Instead of having to write this :
<my-parent prop1="foo" prop2="bar">
<my-children prop1="foo" prop2="bar"></my-children>
<my-children prop1="foo" prop2="bar"></my-children>
</my-parent>
Is it possible ? Thanks.
Props allow data flow only one level. If you want to perpetuate data, you can use an event bus instead.
Instantiate an event bus with an empty Vue instance in your main file.
var bus = new Vue();
Then in your parent, emit the event with data to be passed
bus.$emit('myEvent', dataToBePassed);
Listen for myEventanywhere you want to pick up the data. In your case, it is done in your child components
bus.$on('myEvent', function(data) {
.....
});
Here is my solution, that's probably not a great deal, but that's the cleanest solution for what I want to do right now. The principle is to create computed properties that will use own component prop if they exist, or get $parent values otherwise. The real prop would then be accessible in this._prop.
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
computed: {
_prop1: function() {
return this.prop1 || this.$parent.prop1;
},
_prop2: function() {
return this.prop2 || this.$parent.prop2;
}
}
});
Here is a mixin generator that does that in a more elegant way, and with, possibly, multiple levels :
function passDown(...passDownProperties) {
const computed = {};
passDownProperties.forEach((prop) => {
computed["_" + prop] = function() {
return this[prop] || this.$parent[prop] || this.$parent["_" + prop];
};
});
return { computed };
}
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
mixins: [passDown("prop1", "prop2")]
});
At this point (I'm not a vue expert) I just could think in this solution.
Assign every component's props is boring I agree, so why not doing it programmatically?
// Create a global mixin
Vue.mixin({
mounted() { // each component will execute this function after mounted
if (!this.$children) {
return;
}
for (const child of this.$children) { // iterate each child component
if (child.$options._propKeys) {
for (const propKey of child.$options._propKeys) { // iterate each child's props
// if current component has a property named equal to child prop key
if (Object.prototype.hasOwnProperty.call(this, propKey)) {
// update child prop value
this.$set(child, propKey, this[propKey]);
// create a watch to update value again every time that parent property changes
this.$watch(propKey, (newValue) => {
this.$set(child, propKey, newValue);
});
}
}
}
}
},
});
This works but you will get an ugly vue warn message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
I'm not sure if this is a good solution but it works, so if you decide to use just keep in mind Global-Mixin recomendations:
Use global mixins sparsely and carefully, because it affects every
single Vue instance created, including third party components.
Please see a full example at https://github.com/aldoromo88/PropsConvention
Hope it helps