Dynamically import functions into a component with Vue 3 - javascript

I would like to dynamically (not statically) import functions into a component inside <script setup> ... </script>
Functions are in a pme-check.js file.
import { useStore } from 'vuex';
const store = useStore();
function foo() {
console.log('foo');
}
function bar() {
console.log('bar');
}
export { foo, bar };
In the component, I dynamically import my functions like this and it works correctly
import(`../composables/profil/${store.state.formation.profil}-check`).then(
(module) => {
console.log(module.foo());
// -> 'foo'
console.log(module.bar());
// -> 'bar
}
);
But in this way, my functions cannot be accessed by name in the component.
How to get foo() and bar() anywhere in the <script setup> tags of the component.
Thank you

If you want to use foo() and bar() anywhere in the file then you should use require instead of import and the code should look like this:
const profil= require(`../modules/${store.state.profil}`);
// once you required it, you can use it in any place in the component.
profil.foo();
profil.bar();
Everything works for me in Vue 3.

You can assign the imported functions to refs 1️⃣, and then use the functions through the ref's value 2️⃣:
<script setup>
import { ref, watchEffect } from 'vue'
let foo = ref()
let bar = ref()
watchEffect(() => {
import(`../composables/profil/${store.state.formation.profil}-check`)
.then((module) => {
1️⃣
foo.value = module.foo
bar.value = module.bar
})
})
const fooAndBar = () => {
2️⃣
foo.value()
bar.value()
}
</script>
demo

Related

Change ESM export bindings

I'm wondering how the exported bindings of an ES6 module work when destructuring. I'm exporting an object (thing) as a named export -- but also one of its properties (foo), too, which I'm later updating.
This update works when I import the object and reference its prop, but not when I directly import foo, as shown below. I'm guessing the export const... creates a new, immutable binding, but is there any way to retain its reference to the original object upon export?
// module: 'thing.js'
let thing = {
foo: (x) => console.log(x)
};
const init = () => {
thing.foo = (x) => console.log("updated");
};
export { init, thing };
export const { foo } = thing;
// index.js
import { foo, thing, init } from "./thing";
init();
foo("test"); // does not work
thing.foo("test"); // update is reflected, here
codesandbox
There is not issue with export/import
see this example:
let thing = {
foo: (x) => console.log(x)
};
const init = () => {
thing.foo = (x) => console.log("updated");
};
const foo = thing.foo;
init();
foo("test"); //test
thing.foo("test"); //updated
Variable foo and field thing.foo contains different functions after you rewrite thing.foo inside init function

React/JavaScript Refactor Functions to a Separate File Without Losing Scope

I have a React component with many chained functions for a specific task. Those functions need access to the component's states. I want to separate some of those functions into a new .js file to keep my component file clean, but doing so, the functions lose access to the states.
This is what I have (works fine):
// SomeComponent.js
import React, { useState, useEffect } from 'react';
export default function SomeComponent() {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
return(
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
)
}
One idea is to send the states and state-setters as parameters all the way down to func3, but there are like 10 chained functions in my actual code and that's too messy.
Another idea is to make a class in the new file and instantiate the class with the states and state-setters as attributes. Maybe a new React Component?
Is there a cleaner way I can define func1, func2, func3 in a separate file while keeping access to the states? Either sending the states and state-setters to the scope of NewFile.js somehow, or bringing the functions to the scope of my component. I just want them in the same scope.
My only problem with the functions is the amount of space that they take in my component file.
I want something like this:
// SomeComponent.js
import React, { useState, useEffect } from 'react';
import {func1, func2, func3} from './newFile';
export default function SomeComponent() {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
return(
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
)
}
// NewFile.js
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
export {func1, func2, func3}
In this sort of situation I sometimes like to use a custom hook to separate functions from the rendered JSX. Here, you could do:
export const useSomeComponent = () => {
const [name, setName] = useState("")
const [accessToken, setAccessToken] = useState("")
useEffect(() => {
console.log(name)
func1()
})
function func1() {
console.log(name)
func2()
}
function func2() {
console.log(name)
func3()
}
function func3() {
setAccessToken("1234")
}
return { name, func3 }; // return only the values used externally
};
import { useSomeComponent } from './useSomeComponent';
export default function SomeComponent() {
const { name, func3 } = useSomeComponent();
// note that if you're returning more than one element, a fragment must surround it
return (<>
<h1>{name}</h1>
<button onClick={func3}>Click Me</button>
</>);
}
It makes a big difference when both the function definitions/hooks and the JSX is long, by focusing on only one thing at a time in a file - the layout/markup, or the logic.

How to access Vue 3 global properties from the store

In Vue 2 I used to import Vue and access global properties like this (from the store):
import Vue from 'vue'
Vue.config.myGlobalProperty
According to the new documentation, in Vue 3 the global properties are declared using the app object returned by createApp:
const app = createApp({})
app.config.globalProperties.myGlobalProperty
And then accessed in the child component by simply calling this.myglobalProperty
But how to access that global property from the store? I tried exporting/importing the app object but it doesn't work (probably due to the app being created after its import in the store).
With Vue 2 I used to use global properties in the store like this:
Declaration in the main.js file:
import Vue from 'vue'
Vue.config.myglobalProperty = 'value'
Usage in the store:
import Vue from 'vue'
Vue.config.myglobalProperty
Is there a good way to do that in Vue3?
I noticed a better way to provide/inject properties but it works with child component only and not with the store.
You could pass the app instance to a store factory:
// store.js
import { createStore as createVuexStore } from 'vuex'
export const createStore = (app) => {
return createVuexStore({
actions: {
doSomething() {
if (app.config.globalProperties.myGlobal) {
//...
}
}
}
})
}
And use it in main.js like this:
// main.js
import { createApp } from 'vue'
import { createStore } from './store'
const app = createApp({})
const store = createStore(app)
app.use(store)
app.mount('#app')
If your store modules need access to the app instance, you could use the same technique above with a module factory:
// moduleA.js
export default (app) => {
return {
namespaced: true,
actions: {
doSomething() {
if (app.config.globalProperties.myOtherGlobal) {
//...
}
}
}
}
}
And import it into your store like this:
// store.js
import moduleA from './moduleA'
import { createStore as createVuexStore } from 'vuex'
export const createStore = (app) => {
return createVuexStore({
modules: {
moduleA: moduleA(app),
}
})
}
If you do not use Vuex, etc., you can easily create your store via provide/inject on the application itself, as in the example (the example is simplified for understanding):
const createState = () => reactive({counter: 0, anyVariable: 1});
const state = createState();
const key = 'myState';
// example of reactivity outside a component
setInterval(() => {
state.counter++;
}, 1000);
const app = createApp({});
app.provide('myState', state); // provide is used on the whole application
As you can see, your own store can be completely used outside the component.
Inside components, you can use (example):
setup() {
...
const globalState = inject('myStore'); // reactive => {counter: 0, anyVariable: 1}
...
return {globalState, ...}
}
Accordingly, you can have multiple stores in multiple Vue applications.
I hope this example will help you somehow.

Svelte store function update

Svelte store documentation shows String or Integer being updated, but I did not find any dynamic function in store.
I don't understand how to make the getData function as a writable in order to notify the html of the change.
In the following sample, I would like b to be shown after the updateKey function is called.
You will find a minimal code in REPL here: https://svelte.dev/repl/3c86bd48d5b5428daee514765c926e58?version=3.29.7
And the same code here in case REPL would be down:
App.svelte:
<script>
import { getData } from './store.js';
import { updateKey } from './store.js';
setTimeout(updateKey, 1000);
</script>
<h1>{getData()}!</h1>
store.js
import {setContext} from 'svelte';
import {writable} from 'svelte/store';
var data = {
'a': 'a',
'b': 'b'
};
var key = 'a';
export const getData = function() {
return data[key];
}
export const updateKey = () => {
key = 'b';
}
The goal is to work with a dynamic function in the store.
Well, I think you still have a bit of confusion about how things work in Svelte... Not sure how to best answer your question, so here's some code for what's you're trying to achieve, along with some comments. I hope it will help you better understand how things come together in regards to stores.
App.svelte
<script>
import { onMount } from 'svelte'
import { key, data, updateKey } from './store.js'
onMount(() => {
// it's not safe to have an unchecked timer running -- problems would
// occur if the component is destroyed before the timeout has ellapsed,
// that's why we're using the `onMount` lifecycle function and its
// cleanup function here
const timeout = setTimeout(updateKey, 1000);
// this cleanup function is called when the component is destroyed
return () => {
clearTimeout(timeout)
}
})
// this will log the value of the `key` store each time it changes, using
// a reactive expression (a Sveltism)
$: console.log($key)
</script>
<!--
NOTE: we're using the $ prefix notation to access _the value_ of the store,
and not `data`, which would be _the store itself_ (an object with
subscribe, set, etc.)
-->
<h1>{$data}</h1>
store.js
import { writable, derived } from 'svelte/store'
const db = {
'a': 'a',
'b': 'b'
}
// a writable store with initial value 'a'
export const key = writable('a')
export const updateKey = () => {
// a writable store has a `set` method to change its value
key.set('b')
}
// you can use a derived store to compute derived values from
// the current value of other stores
//
// here, we're getting the value from the db when the value of
// the `key` store changes
export const data = derived([key], ([$key]) => db[$key])
if I understood your question correctly, you want to be able to change the function (logic) that is executed by getData() and you want on each function change the html to be updated
for this use case you'll need to create your own custom store
as follows in store.js
import { writable } from 'svelte/store';
// an object to hold our functions
const functions = {
"funcA": () => {
// do something
return "whatevedata for a"
},
"funcB": () => {
// do something
return "the data of b"
}
}
// this how to create a custom store, taken from svelte documentation
function createCustomStore(defaultValue) {
const { subscribe, set, update } = writable(defaultValue);
return {
subscribe,
//custom function change func where suppliedValue the value you input to the store
// set() is a default function for a store to change it's value
changeFunc: (suppliedValue) => set(functions[suppliedValue]),
reset: () => set(defaultValue)
};
}
export const getData = createCustomStore(() => "default");
export const updateKey = () => {
// this to update which function the store uses
getData.changeFunc("funcB")
}
in App.svelte
<script>
import { getData } from './store.js';
import { updateKey } from './store.js';
setTimeout(function() {
updateKey()
}, 1000);
</script>
<h1>{$getData()}</h1>
we added the $ to getData because it's a store that holds reference to functions and the () is there to execute any function referenced by getData store. since it is a store on each value change (function change) of getData, the html will be updated
here is a repl of the implementation

difference between export default { name } and named export { name }

I am trying to deprecate a module with many named exports like so:
const Foo = () => 'foo';
const Bar = () => 'bar';
export { Foo };
export { Bar };
which is fine when consuming via:
import { Foo, Bar } from './something';
My thoughts about enabling deprecation warnings was to use a default export of type object with a property getter override for each key that prints the deprecation and then returns the module.
The shape then becomes something like:
const something = {};
Object.defineProperty(something, 'Foo', {
get(){
console.warn('Foo is deprecated soon');
return Foo;
}
});
// etc
export default something;
My thinking had been that the destructuring import would figure it out so
import { Foo, Bar } from './something';
... would continue to work as before. Instead, webpack complains that something does not have a named export Foo or Bar
using this, however, works:
const { Foo, Bar } = require('./something');
I can also have import something from './something'; const { Foo, Bar } = something and that works but it defeats the purpose if I have to refactor every import that exists.
So the question really is about how import { Foo, Bar } from './something'; works compared to the require() - I'd have thought that if the default export is an object, it would figure it out and destructure, but no joy.
Is there an easy way to do this 'proxying' without changing how the exports are being consumed elsewhere?
I think i made it work. Bare in mind that this is a workaround.
Given that you said that the library is being "re-exported" from a single file you could add an additional "layer" to the "re-export" where we turn the "re-exportation" file into a JS file and write our own associated typing file for it.
Working repl: https://repl.it/#Olian04/CelebratedKlutzyQuotes
Code snippets:
// index.ts
// consuming the library
import { Foo } from './demo';
console.log(Foo());
// library.ts
// the library it self
const Foo = () => 'foo';
export { Foo }
// demo.js
// the workaround implementation
const depricatedLibrary = require('./library');
const something = new Proxy(depricatedLibrary, {
get(obj, key) {
if (typeof key === 'string') {
console.warn(key + ' is deprecated soon');
}
return obj[key];
}
});
module.exports = something;
// demo.d.ts
// the workaround types
export * from './library';

Categories