Vue3 Multiple Instances of Composition API Data Store - javascript

Simplifying a real-life situation...
Let's say I have a webapp with two columns. The same component used in both columns. The functionality uses data storage and functions created in a separate composition api js file, made available to the component by importing and then provide/inject. Works great.
But is there a way to write the js file with the composition api once, and then create multiple instances when it's imported to the Vue app? That way a separate instance can be sent to each component and they won't share the same data object. I know if you import the same file with multiple names...
import instanceone from "path";
import instancetwo from "path";
...they'll both share the same objects because it's importing the same file as two names, not two instances of the file.
Is there any way to achieve something like this? I'm interested in any setup that would achieve the end goal (not needing two copies of the file to achieve two independent usages). I took a flyer and thought maybe creating a single file that exports objects and functions, then two files that each import the appropriate pieces of that single file, and then let Vue import those two files might work...but nope, not so much.
Obviously there are plenty of other ways to do this, but I want to explore this possibility first. Preferably without making use of Vuex.
Thank you!

the following is one of the way to achieve this
/* composable_module.js */
import { ref, computed } from 'vue';
export const shared_var_1 = ref(0);
export const shared_var_2 = ref(0);
export function composable_variables() {
// will return separate instance of variables for each call
const var1 = ref(0);
const comp_var1 = computed(() => var1.value + shared_var_1.value);
// comp_var1 updates value when either var1 or shared_var_1 value gets updated
return { var1, comp_var1 };
}
usage as following
/* component_1.vue */
import { shared_var_1, shared_var_2, composable_variables } from 'composable_module.js';
/* other things needed for component or any file */
setup() {
const { var1, comp_var1 } = composable_variables();
/*
Do what you want to do with
shared_var_1, shared_var_2, var1, comp_var1
*/
// return whatever you wanted to use in template
return { shared_var_1, shared_var_2, var1, comp_var1 }
}
Here shared_var_1, shared_var_2 will act as vuex store values
and var1, comp_var1 will be separate for each function call
so can be used in multiple components as separate variable sharing common functionality but not value.

Within your 'path' composable you could define two states, then call the relevant state with something like:
const { getItem1, getItem2, setItem1, setItem2 } = (whichInstance) ? instanceOne : instanceTwo
You just need to define your whichInstance condition to determine which instance you want.
Your composable could be something like:
const stateOne = reactive({
item1: true,
item2: 1
})
const stateTwo = reactive({
item1: false,
item2: 2
})
export function instanceOne() {
let stateRef = toRefs(stateOne)
/* Getters */
const getItem1 = () => {
return stateRef.item1
}
const getItem2 = () => {
return stateRef.item2
}
/* Mutations */
const setItem1 = (value) => {
stateRef.item1.value = value
}
const setItem2 = (value) => {
stateRef.item2.value = value
}
return {
state: toRefs(stateOne),
getItem1,
getItem2,
setItem1,
setItem2
}
}
export function instanceTwo() {
let stateRef = toRefs(stateTwo)
/* Getters */
const getItem1 = () => {
return stateRef.item1
}
const getItem2 = () => {
return stateRef.item2
}
/* Mutations */
const setItem1 = (value) => {
stateRef.item1.value = value
}
const setItem2 = (value) => {
stateRef.item2.value = value
}
return {
state: toRefs(stateTwo),
getItem1,
getItem2,
setItem1,
setItem2
}
}
})

Related

Sharing variables between functions in javascript

if this question is not clear, let me know and I will exapand on it. Not great with JS.
I have a js file. let's call it jsFile1.js in which I have two methods. Method1 is being called from another file (anotherJsFile.js) and that call sends a variable to the Method1 in jsFile1.js.
Now I want my second method, Method2, which is being called from inside jsFile1.js to also be able to use the variable sent from anotherJsFile to method 1.
Have tried using id's and set value etc but it won't work. Any suggestions? Presume I have to store const tmp in the config or init and then access it from the Method2?
File1
Method1(item, table) {
//item is a marked item from the table, table contains all entries
const tmp = {table, id: "tmpTable"};
}
Method2() {
const data = this.$$("tmpTable").getValues();
}
config() {
const Method2Button = {
view:"button",
label:"Method2",
click: () => this.Method2()
}}
Just write a method that takes an integer and import the class in you constructor
File1.js
private test: any
Method1(item, table) {
const tmp = {table, id: "tmpTable"};
this.test = tmp
}
Method2() {
return this.test.id //.getValues() ??
// return this.class.getValues(this.test.id)
}
config() {
const Method2Button = {
view:"button",
label:"Method2",
click: () => this.Method2()
}}

How to store nuxtjs dynamically generated routes in vuex store

I'm trying to leverage nuxtjs SSG capabilities by creating a static web site where the pages content and navigation are fetched from an API.
I already found my way around on how to dynamically generate the routes by defining a module where I use the generate:before hook to fetch the pages content and routes. When creating the routes I store the page content as the route payload. The following code does just that and works as intended.
modules/dynamicRoutesGenerator.js
const generator = function () {
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
generator.generateRoutes(await generateDynamicRoutes())
})
}
let generateDynamicRoutes = async function() {
//...
return routes
}
export default generator
Now the problem I'm facing is that I have some navigation components that need the generated routes and I was thinking to store them into the vuex store.
I tried the generate:done hook but I don't know how to get the vuex store context from there. What I ended up using was the nuxtServerInit() action because as stated in the docs:
If nuxt generate is ran, nuxtServerInit will be executed for every dynamic route generated.
This is exactly what I need so I'm trying to use it with the following code:
store/index.js
export const actions = {
nuxtServerInit (context, nuxtContext) {
context.commit("dynamicRoutes/addRoute", nuxtContext)
}
}
store/dynamicRoutes.js
export const state = () => ({
navMenuNivel0: {}
})
export const mutations = {
addRoute (state, { ssrContext }) {
//Ignore static generated routes
if (!ssrContext.payload || !ssrContext.payload.entrada) return
//If we match this condition then it's a nivel0 route
if (!ssrContext.payload.navMenuNivel0) {
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Store nivel0 route, we could use url only but only _id is guaranteed to be unique
state.navMenuNivel0[ssrContext.payload._id] = {
url: ssrContext.url,
entrada: ssrContext.payload.entrada,
navMenuNivel1: []
}
console.log(JSON.stringify(state.navMenuNivel0, null, 2));
//Nivel1 route
} else {
//...
}
}
}
export const getters = {
navMenuNivel0: state => state.navMenuNivel0
}
The action is indeed called and I get all the expected values, however it seems like that with each call of nuxtServerInit() the store state gets reset. I printed the values in the console (because I'm not sure even if it's possible to debug this) and this is what they look like:
{}
{
"5fc2f4f15a691a0fe8d6d7e5": {
"url": "/A",
"entrada": "A",
"navMenuNivel1": []
}
}
{}
{
"5fc2f5115a691a0fe8d6d7e6": {
"url": "/B",
"entrada": "B",
"navMenuNivel1": []
}
}
I have searched all that I could on this subject and altough I didn't find an example similar to mine, I put all the pieces I could together and this was what I came up with.
My idea was to make only one request to the API (during build time), store everything in vuex then use that data in the components and pages.
Either there is a way of doing it better or I don't fully grasp the nuxtServerInit() action. I'm stuck and don't know how to solve this problem and can't see another solution.
If you made it this far thanks for your time!
I came up a with solution but I don't find it very elegant.
The idea is to store the the API requests data in a static file. Then create a plugin to have a $staticAPI object that expose the API data and some functions.
I used the build:before hook because it runs before generate:before and builder:extendPlugins which means that by the time the route generation or plugin creation happen, we already have the API data stored.
dynamicRoutesGenerator.js
const generator = function () {
//Add hook before build to create our static API files
this.nuxt.hook('build:before', async (plugins) => {
//Fetch the routes and pages from API
let navMenuRoutes = await APIService.fetchQuery(QueryService.navMenuRoutesQuery())
let pages = await APIService.fetchQuery(QueryService.paginasQuery())
//Cache the queries results into staticAPI file
APIService.saveStaticAPIData("navMenuRoutes", navMenuRoutes)
APIService.saveStaticAPIData("pages", pages)
})
//Before hook to generate our custom routes
this.nuxt.hook('generate:before', async (generator, generatorOptions) => {
console.log('generate:before')
generator.generateRoutes(await generateDynamicRoutes())
})
}
//Here I can't find a way to access via $staticAPI
let generateDynamicRoutes = async function() {
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
//...
}
The plugin staticAPI.js:
import APIService from '../services/APIService'
let fetchPage = function(fetchUrl) {
return this.pages.find(p => { return p.url === fetchUrl})
}
export default async (context, inject) => {
//Get routes and files from the files
let navMenuRoutes = APIService.getStaticAPIData("navMenuRoutes")
let pages = APIService.getStaticAPIData("pages")
//Put the objects and functions in the $staticAPI property
inject ('staticAPI', { navMenuRoutes, pages, fetchPage })
}
The APIService helper to save/load data to the file:
//...
let fs = require('fs');
let saveStaticAPIData = function (fileName = 'test', fileContent = '{}') {
fs.writeFileSync("./static-api-data/" + fileName + ".json", JSON.stringify(fileContent, null, 2));
}
let getStaticAPIData = function (fileName = '{}') {
let staticData = {};
try {
staticData = require("../static-api-data/" + fileName + ".json");
} catch (ex) {}
return staticData;
}
module.exports = { fetchQuery, apiUrl, saveStaticAPIData, getStaticAPIData }
nuxt.config.js
build: {
//Enable 'fs' module
extend (config, { isDev, isClient }) {
config.node = { fs: 'empty' }
}
},
plugins: [
{ src: '~/plugins/staticAPI.js', mode: 'server' }
],
buildModules: [
'#nuxtjs/style-resources',
'#/modules/staticAPIGenerator',
'#/modules/dynamicRoutesGenerator'
]

JS ES6: How to give child methods access to their sibblings?

I have multiple utility methods like
export const makeTextUtils = ({ U }) =>
Object.freeze({
hello: () => "Hello World!",
lorem: () => "Lorem ipsum..."
)};
these child utils can reference each other
export const makeOutputUtils = ({ U }) =>
Object.freeze({
logHello: () => console.log(U.txt.hello())
)};
Now I want to expose the Utils in a utils.js and inject the parent Method into all children
import { makeTextUtils } from './text';
import { makeOutputUtils } from './output';
// dependency injections
const textUtils = makeTextUtils({ U });
const outputUtils = makeTextUtils({ U });
// building parent util method
export const U = Object.freeze({
txt: textUtils,
out: outputUtils
});
I've tried various ways of importing the U at the top of the main file and switching up the order within the file, but nothing seems to do the trick.
Any help would be much appreciated.
First declare the U object at the top so that you can pass it down to the other functions. Then, after U gets properties assigned to it, you can freeze it:
import { makeTextUtils } from "./text";
import { makeOutputUtils } from "./output";
export const U = {};
// dependency injections
const textUtils = makeTextUtils({ U });
const outputUtils = makeOutputUtils({ U });
// building parent util method
Object.assign(U, {
txt: textUtils,
out: outputUtils
});
Object.freeze(U);
U.out.logHello();
https://codesandbox.io/s/happy-benz-cu3n5
The passing of U inside an object and immediately destructuring it in the utility functions doesn't seem to do anything - unless there's a particular reason for that, feel free to just pass the U object alone (and to use the U parameter alone).

What is the meaning of this strange syntax [SOME_MUTATION] (state) for function definition in JavaScript?

In Vue I see code like:
// mutation-types.js
export const SOME_MUTATION = 'SOME_MUTATION'
and
// store.js
import Vuex from 'vuex'
import { SOME_MUTATION } from './mutation-types'
const store = new Vuex.Store({
state: { ... },
mutations: {
// we can use the ES2015 computed property name feature
// to use a constant as the function name
[SOME_MUTATION] (state) {
// mutate state
}
}
})
Where the syntax [SOME_MUTATION] (state) comes from? Is [SOME_MUTATION, ANOTHER] (state) also valid syntax? Why SOME_MUTATION (state) (which is valid function syntax) is not used?
How in large Vue project, putting all export const SOME_MUTATION = 'SOME_MUTATION' defintions in separate file really help instead of putting in same file (store.js) ?
In this line
import { SOME_MUTATION } from './mutation-types'
where you are importing the types into SOME_MUTATION, it has a string value. Now to define a function with this name inside an object you need to do
{
[SOME_MUTATION](state) {
}
}
Would be same as (if SOME_MUTATION has a value say changeState)
{
changeState: function(state){
}
}
And state is by default passed as an argument in mutations.
Read more here
We prefer this type of architecture in a Vue application so as to avoid name conflicts in defining Mutations and Actions and Getters across multiple components.
It's a javascript syntax for defining an object key programatically/dynamically.
for example, you have a variable:
var myFunctionName = "getUsers";
you can actually create a function inside an object using the value of a variable as the function name.
So for example,
{
[myFunctionName]() {
// do something
}
}
will actually become:
ES6:
{
getUsers() {
// do something
}
}
or ES5:
{
getUsers: function() {
// do something
}
}
This is not limited to creating functions within an object, but it can be applied to any property within your object.
For example:
let myPropertyName = 'user';
{
[myPropertyName]: {
name: 'Hello',
age: 10
}
}
will become:
{
user: {
name: 'Hello',
age: 10
}
}

React to nested state change in Angular and NgRx

Please consider the example below
// Example state
let exampleState = {
counter: 0;
modules: {
authentication: Object,
geotools: Object
};
};
class MyAppComponent {
counter: Observable<number>;
constructor(private store: Store<AppState>){
this.counter = store.select('counter');
}
}
Here in the MyAppComponent we react on changes that occur to the counter property of the state. But what if we want to react on nested properties of the state, for example modules.geotools? Seems like there should be a possibility to call a store.select('modules.geotools'), as putting everything on the first level of the global state seems not to be good for overall state structure.
Update
The answer by #cartant is surely correct, but the NgRx version that is used in the Angular 5 requires a little bit different way of state querying. The idea is that we can not just provide the key to the store.select() call, we need to provide a function that returns the specific state branch. Let us call it the stateGetter and write it to accept any number of arguments (i.e. depth of querying).
// The stateGetter implementation
const getUnderlyingProperty = (currentStateLevel, properties: Array<any>) => {
if (properties.length === 0) {
throw 'Unable to get the underlying property';
} else if (properties.length === 1) {
const key = properties.shift();
return currentStateLevel[key];
} else {
const key = properties.shift();
return getUnderlyingProperty(currentStateLevel[key], properties);
}
}
export const stateGetter = (...args) => {
return (state: AppState) => {
let argsCopy = args.slice();
return getUnderlyingProperty(state['state'], argsCopy);
};
};
// Using the stateGetter
...
store.select(storeGetter('root', 'bigbranch', 'mediumbranch', 'smallbranch', 'leaf')).subscribe(data => {});
...
select takes nested keys as separate strings, so your select call should be:
store.select('modules', 'geotools')

Categories

Resources