Where to put global helper js functions - javascript

I have several helper functions I intend to use throughout my Vue.js 2 application (i.e. within components and vuex). The one I'm using as an example here, toCurrency, converts a number to a currency string. I'm wondering where the best place to put this helper function is. I'm currently putting it at the top of my store.js file (I'm using vuex obviously) and calling it throughout the file. However, this means I also need to define it as a method when I want to use it in components. Is there a better place to put them so I don't have to keep defining it or is this the best place?
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
function toCurrency(num){
return "$" + num.toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
}
export default new Vuex.Store({
state: {
price: 2
},
getters: {
numString: state => {
return toCurrency(state.funds)
}
},
mutations: {
decrementOne: state => {
state.num --;
alert("Price decremented to " + toCurrency(state.funds))
}
}
})

This sounds like a perfect case for Instance Variables. Essentially I would firstly setup an address such as this.$helperfunctions and then proceed to add all of my methods directly onto this instance member after which they will become available to the rest of your application.
import myObjectFullOfMethods from '../somewhere/foo/bar.js';
new Vue({
beforeCreate: function() {
this.$helperfunctions = new myObjectFullOfMethods();
}
})

Related

How to use separate stores for each app instance on the same page

I have a fairly complex VueJS2 application, that uses a vuex store.
Up until now, everything worked perfectly. Now I have to change the application to support multiple instances on one page.
It should be dynamic, which means the customer should be able to insert as many instances as he wants by simply adding the required HTML code. This is already possible since we use https://www.npmjs.com/package/vue-custom-element
The main problem of course is Vuex. there is, quite understandable, only one instance. And since the application instances are all the same component, I can not easily add separate Vuex instances.
To paint a clearer picture:
The Vuex store is being generated like this:
Vue.use(Vuex)
export const createStore = () => {
return new Vuex.Store({
store: () => ({
/* the data */
})
/* modules, getters, mutations, and simmilar */
})
}
The VueJS application is being generated normally:
app.vue:
export default {
store: createStore(),
/* Other stuff */
}
main.js:
Vue.customElement('my-app', app)
I know I could refactor the application to use dynamically registered modules, but it would be quite a big change and I am hoping for an easier solution, at least temporarily until we refactor the application.
Is there a quicker solution than dynamic modules? I am grateful for every suggestion.
I found a solution. I believe it is somewhat hacky, but it works.
If you plan to have multiple application instances on the same page I strongly recommend that you design your VueJS application accordingly. However, if you are in a pickle like I was, this might be a quick solution, that works well.
First, you need a function, that returns a store. If you export your store directly you will still use the same instance!
Vue.use(Vuex)
export const createStore = () => {
return new Vuex.Store({
store: () => ({
/* the data */
})
/* modules, getters, mutations, and simmilar */
})
}
In your main module, you have to create and inject a separate store into each app instance. The best way to do this is to create and inject a store in the "beforeCreate" hook:
export default {
// ...
beforeCreate() {
this.$store = createStore();
},
};
I found this solution on Github:
https://github.com/vuejs/vuex/issues/414#issue-184491718

Would I lose any benefits if I switch from Vuex to custom Javascript store?

I am making a game editor. All it needs is a way to save and read data from a common store, such as sprites and tool settings.
The problem is Vuex just seems really messy to me. Maybe it's because I'm not building a standard SPA which Vuex was designed for, but it just seems that every time I want to do something simple it adds 50+ lines of code in getters, actions, and mutations that would otherwise be unnecessary. On top of that, it has limitations such as not being able to modify states from getters which would be really helpful when generating unique asset IDs. I also have no need for the dynamic loading/unloading of modules.
So my question, if I replaced Vuex with an imported object like the following:
class MyStore_Class{
constructor(){
this.val = 0;
}
//other methods and stuff to manipulate data
}
let MyStore = new MyStore();
export default MyStore;
Then imported this MyStore object into the components where I needed it, would I lose anything?
I ran some simple tests and it seems like it works perfectly as a drop in replacement for Vuex, but I'm afraid there might be some kind of downside that I would notice only later down the line.
EXIT: Pretty much all data for the app is local, so the separation of actions/mutations tends to mean that the only action code I am writing is commit('doMutation', newData) over and over again
Your solution could be a vue observable, really easy to do and lightweight in term of architecture ;)
Create a store.js file in your src/root folder
Create the state value/s you wish to have globally
Create the methods you needs for his interaction
Set it up in your components and there you go
Setup store.js
import Vue from "vue";
const state = Vue.observable({ val: 0 });
export const increment = () => state.counter++;
export const decrement = () => state.counter--;
export default state;
In your component
<template>
<div>
<p>The value is {{val}}</p>
<button #click="inc">+</button>
<button #click="dec">-</button>
</div>
</template>
<script>
import store, { increment, decrement } from "./store";
export default {
computed: {
// your getter in some way
counter() {
return store.counter;
}
},
methods: {
inc() {
increment();
},
dec() {
decrement();
}
}
};
</script>
I took these examples on this article, where you could read more about vue observable if you want, but i use it a lot on small projects where i need just few values accessible globally and that doesn't require a vuex architecture.
https://medium.com/better-programming/how-to-manage-vues-state-with-vue-observable-25988a88938b

Vue 3 composition API and access to Vue instance

In main.js I have something like this:
import { myUtilFunc} from './helpers';
Object.defineProperty(Vue.prototype, '$myUtilFunc', { value: myUtilFunc });
In this way I have acess to myUtilFunc across whole application with this.$myUtilFunc
But how can I achieve the same in setup() method in Vue 3 if I don't have access to this?
Use provide/inject
Provide
const app = createApp(App);
app.provide('someVarName', someVar); // `Provide` a variable to all components here
Inject:
// In *any* component
const { inject } = Vue;
...
setup() {
const someVar = inject('someVarName'); // injecting variable in setup
}
Note that you don't have to provide from the app root, but can also provide from any component to only its sub-components:
// In *any* component
setup() {
...
},
provide() {
return {
someVarName: someVar
}
}
Original answer
[Edit: While my original answer below is still useful for context properties, it's no longer recommended to use context.root, which is no longer mentioned in the guide and may soon be deprecated.]
In Vue 3, setup has an optional second argument for context. You can access the Vue instance through context.root instead of this:
setup(props, context) {
context.root.$myUtilFunc // same as `this.$myUtilFunc` in Vue 2
}
Things you can access through context:
context.attrs
context.slots
context.parent
context.root
context.emit
While Dan's answer is correct, I would like to provide an alternative mentioned in the comments to the accepted answer. There are pros and cons to each, so, you need to choose based on your needs.
To understand why the code below works, it is important to remember, that provided properties are transitive in the tree of components. I.e. inject('foo') will look for 'foo' in every parent going up the hierarchy all the way to the app; there is no need to declare anything in the middle wrappers.
So, we can write something like this, where globalDateFormatter() is just an example function we want to use in any component down the tree:
main.js
import { createApp } from 'vue'
import App from './App.vue'
const globalDateFormatter = (date) => {
return '[' + date.toLocaleString() + ']'
}
const app = createApp(App)
app.provide('globalDateFormatter', globalDateFormatter) // <-- define here
app.mount('#app')
And then, in some DeepDownComponent.vue:
<template>
<p> {{ fmt(new Date()) }} </p>
</template>
<script>
import { inject } from 'vue'
export default {
setup(){
const fmt = inject('globalDateFormatter', x => x.toString())
// ^-- use here, optional 2nd parameter is the default
return {fmt}
}
}
</script>
Obviously, you can directly import and use provide and inject with the following signatures
provide<T>(key: InjectionKey<T> | string, value: T): void
and
inject<T>(key: InjectionKey<T> | string, defaultValue: T): T
anywhere in your code, doesn't have to be app.provide()
You can also provide values, even the global store, like this, just don't forget to use ref() or reactive() as needed.
In short, whenever you would prefer dependency injection, provide/inject are your friends.

How to import Arrays from my Vue Components to store.js (State management with Vuex)

Good evening guys. :)
I've got a small Problem.
I simply have created an Array in a Stocks Vue Component which I made and now I want to Import the Array in my store.js file where I centralized all the necessary data (Vuex).
If there are any more Questions, just ask me. ^^
where i centralized all the neccessary data (Vuex).
If you want the array in your store why don't you initialize it there instead of your Stocks.vue component? I can't tell much about your app with just a .png. Is there a specific reason you cannot start with the array in the store?
If you had to leave it this way you could set a null value in your store. Something like:
state: {
funds: 10000,
stocks: null
}
Then write a mutation:
mutations: {
SET_STOCKS (state, payload) {
state.stocks = payload;
}
}
Then you could commit the mutation in your component where the payload would be the value of your array. You can get around using mutations by manipulating the state object directly, but it is not recommended.
Nathan's suggestion is a right one (and the Vue way to approach this). One small thing to consider though - what's the benefit of moving that data to your state? It adds complexity and unless other components also need access to it (and you can't pass it down via props), there's no real reason to move data out from a component-level. Not all app's state/data has to be centralized in your state-management system (vuex in this case).
If, however, the data should be globally (app-wide) accessible, then you should declare the stocks array already state-side and in your component Stocks.vue simply map the state:
// store.js
export const store = new Vuex.store({
state: {
funds: 10000,
stocks: [
// Stocks data goes here
]
},
// ...
})
And then map it in your component:
// Stocks.vue
<script>
import { mapState } from 'vuex'
export default {
name: 'stocks',
computed: {
...mapState({
stocks: state => state.stocks
})
}
}
</script>
You can now access stocks in Stocks.vue in your template block or this.stocks in your script block (e.g. under a method).
PS! Welcome to StackOverflow : )
Have you checked the documentation?
The basic idea you need to import the store from the component in which you want to read the store.

MobX store doesn't get updated in react

What i want to achieve
I have a store object and i want to populate one of it's properties (which is an empty array) with instances of another object. And i want one of my react component automatically updated when part of the mentioned array instance is changed.
What my problem is
By logging out the value of this constructed store object i can see the change in the browser console, but it doesn't get updated automatically in the react component when its value changed.
So i'd like to get hints, examples of how to implement a solution like this in general.
Details
My project
I want to create a MobX store called Session which would store everything my react webapp needs.
This webapp would be a document handling tool, so after creating new or loading existing documents i want to add the Document object to the Session (into an object array called documents).
Some more details: a Document consists of one or more section. So using a WYSIWYG editor i add its content to the given section every time it's content changes.
Problem
I can add a new Document to the Session, i can update section as well(I can log out the value of a section in console), but using the Session reference to this document and section in a react component it doesnt update its state when section value is changed.
To my understanding in my example the reference of a Document is not changed when the value of a section is changed and hence it doesn't trigger MobX to react.
What i found so far
I started to dig in the deep, dark web and found this article.
So i started getting excited since asStructure (or asMap) seemed to solve this issue, but it looks like asStructure in MobX is deprecated.
Then i found this issue thread, where a thing called observable.structurallyCompare is mentioned. But again i found nothing about this in MobX documentation so im puzzled how to implement it.
So im stuck again and have no idea how to solve this problem.
Code excerpts from my project
This is how i reference to the mentioned Session value in the main react component:
import userSession from '../client/Session';
import {observer} from 'mobx-react';
#observer class App extends React.Component {
...
render() {
return (
...
<div>{JSON.stringify(userSession.documents[0].content.sections)}</div>
...
This is how i update the section in the editor react component:
import userSession from '../../client/Session';
...
handleChange(value,arg2,arg3,arg4) {
this.setState({ content: value, delta: arg4.getHTML()});
userSession.documents[0].updateSectionContent(this.props.id, this.state.delta);
}
}
...
Session.js excerpt:
class Session {
constructor(){
extendObservable(this, {
user: {
},
layout: {
},
documents: []
})
//current hack to add an empty Document to Session
this.documents.push(new Document());
}
//yadayadayada...
#action addNewSection() {
userSession.documents[0].content.sections.push({
type: "editor",
id: userSession.documents[0].getNextSectionID(),
editable: true,
content: "",
placeholder: "type here to add new section..."
});
}
}
var userSession = window.userSession = new Session();
export default userSession;
Document.js
import {extendObservable, computed, action} from "mobx";
import userSession from "./Session";
class Document {
constructor(doc = null) {
if (doc == null) {
console.log("doc - no init value provided");
extendObservable(this,{
labels: {
title: "title",
description: "description",
owners: ["guest"]
},
content: {
sections: [
{
type: "editor",
id: "sec1",
editable: true,
placeholder: "this is where the magic happens"
},
]
}
})
} else {
console.log("doc - init values provided: ");
extendObservable(this,{
labels: doc.labels,
content: doc.content
});
}
}
getNextSectionID(){
return `sec${this.content.sections.length}`;
}
#action updateSectionContent(sectionID, delta) {
console.log("doc - looking for section " + sectionID + " to update with this: " + delta);
let index = this.content.sections.findIndex(
section => section.id === sectionID
);
userSession.documents[0].content.sections[index].content = delta;
}
}
export default Document;
Ps.: atm moment i don't remember why i made Document properties observable, but for some reason it was necessary.
Unfortunately, you are implementing mobx with React the incorrect way. To make it more comprehensible, I suggest you look into the implementation of the HOC observer that mobx-react provide.
Basically, what this HOC does is to wrap your component inside another React component that implement shouldComponentUpdate that check when the props referred inside render function change, and trigger the re-render. To make React component reactive to change in mobx store, you need to pass the store data to them as props, either in React traditional way, or via the inject HOC that mobx-react provide.
The problem while your code does not react to change in the store is because you import the store and use them directly inside your component, mobx-react cannot detect change that way.
To make it reactive, instead of import it and use it directly in your App component, you can pass the store as a prop as follow:
#observer
class App extends React.Component {
...
render() {
return (
...
<div>{this.props.sections}</div>
...);
}
}
Then pass the store to App when it's used:
<App sections={userSession.documents[0].content.sections} />
You can also have a look at how to use inject here: https://github.com/mobxjs/mobx-react.
An just a suggestion: before jumping directly on some best pattern, try to stick with the basic, standard way that library author recommend and understand how it works first, you can consider other option after you got the core idea.

Categories

Resources