I am wondering if i would be able to access my Svelte store values from a plain .js file.
I am trying to write functions returning a dynamic value based on a store value, to import them in any component.
But in a plain .js file I can't just access the store value with the $ sign..
Quick exemple of a basic function that uses a store value and could be used on multiple components:
//in .svelte
function add() {
$counter = $counter + 1;
}
EDIT: rephrasing a bit
EDIT:
Found a solution but i don't really know if it's really optimized..
//in .js file
import { get } from "svelte/store";
import { counter } from "./stores";
export function add() {
var counterRef = get(counter);
counter.set(counterRef + 1);
}
Yes, absolutely.
For one thing, the store API is very simple and nothing prevents you from subscribing to the store yourself to know the value:
import myStore from './stores'
myStore.subscribe(value => {
// do something with the new value
// you could store it for future reference...
})
And, if you just want to know the current value, Svelte has a helper for that, the get function:
import { get } from 'svelte/store';
import myStore from './stores'
const value = get(myStore);
In addition to rixo's answer, a better way to implement add is to use the store's update method:
import { counter } from "./stores";
export function add() {
counter.update(n => n + 1);
}
You could also create a custom store that implemented that logic.
It's not exactly what you asked for (import) but this method serves same purpose:
you pass your store as argument, so no need to import in the .js your store
import {get} from 'svelte/store'
export function add(yourStore) {
let _yourStore = get(yourStore)
yourStore.set(_yourStore + 1)
}
Then you just have to import your store in your Svelte component.
It allows not to care about store imports in your .js, but only on your component.
Many Svelte store examples don't use objects, so here's how I got it working. This uses an async fetch to update the user's locale to 0. Imagine pstats = {uid: 1, locale: 5}...
import { pstats} from './stores.js'
export async function leave_locale() {
return fetch(`./builds/leave`, {method: 'get'})
.then(res => res.json())
.then(res => {
pstats.update((theStore) => {
return Object.assign(theStore, {locale: 0});
})
})
}
Related
I want to use the package called "youtube-music-api", however I can't get it to work within React. The code resides within a React component that triggers it when the component mounts. The code I used is as follows:
const YoutubeMusicApi = require('youtube-music-api')
function fetchResults(data) {
const api = new YoutubeMusicApi()
api.initalize()
.then(info => {
api.getSearchSuggestions(data).then(result => {
console.log(result)
})
})
}
However, it ends up failing when I try to run it, I receive the following:
I tried using the package outside of React, and it performed successfully. So, I suspect that the issue may be because with React I am serving the page to the user where they then perform the API call. How might I go about using the package within React?
I suggest using the faker.js package. you can easily get fake data according your use case.
import faker from 'faker'
const [data,setData] = useState([])
useEffect(() => {
var randomName = faker.name.findName(); // Rowan Nikolaus
var randomEmail = faker.internet.email(); // Kassandra.Haley#erich.biz
var randomCard = faker.helpers.createCard(); // {avatar , name etc..}
setData({ name : randomName , email : randomEmai , card : ...randomCard })
},[]);
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
}
}
})
K6 Tool is being used for our testing needs. For the sample snippet below when run with K6, we see that the change occurred in default function for data passed from setup is not affected and visible in tear down stage. Is there any other possible way to have this available so that we could utilize it for Test Data Management purposes during Load Tests?
import http from "k6/http";
import { group, check, sleep, fail } from "k6";
import papaparse from 'https://jslib.k6.io/papaparse/5.1.1/index.js';
import { textSummary } from "https://jslib.k6.io/k6-summary/0.0.1/index.js";
const SLEEP_DURATION = 1;
// Global variables should be initialized.
var csvData = [{"Id":"P976O6RP8NSFN076RKLBSGK1Q8NZT7WA"},{"Id":"D4YJZFQJUPJTZWPLP2VB5M839SZBNNSO"}];
function removeId(arr, value) {
arr = arr.filter(function(item) {
return item.Id !== value
});
return arr;
}
export function setup() {
return JSON.parse(JSON.stringify(csvData));
}
export default function(data) {
let Id = csvData[0].Id;
data = removeId(data, Id);
console.log('ID deleted is: '+Id+' \nJson<data> in default :: '+JSON.stringify(data));
}
export function handleSummary(data) {
return {
//stdout: JSON.stringify(data),
};
}
export function teardown(data) {
//Teardown does not have modified data object updated in default function
console.log('\nJson<data> in teardown :: '+JSON.stringify(data));
}
No, this is (currently?) not possible. The setup data must not be modified by the default function. Think about it: this wouldn't work, if executed in a cloud or clustered environment, because the setup and default function might be executed on different machines.
Why do you want to modify the setup data?
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'
]
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).