Nuxt3 "Never define ref() outside of <script setup>", then how? - vue.js

from nuxt 3 documentation,
https://nuxt.com/docs/getting-started/state-management
I'm told that I should never define ref outside script setup
since it will "be shared across all users visiting your website and can lead to memory leaks!"
I want to use vueuse's useBreakpoints,
https://vueuse.org/core/useBreakpoints/
I simply put them in composable and export,
and happily use them all across components.
but I see their type is globalThis.Ref
is it safe to use them as is,
or am I in big trouble as nuxt doc says?
// file: composables/useMedia.ts
import { breakpointsTailwind, useBreakpoints } from '#vueuse/core'
const breakpoints = useBreakpoints(breakpointsTailwind)
export const isDesktop = breakpoints.greaterOrEqual('lg')
export const isTablet = breakpoints.greaterOrEqual('sm') && breakpoints.smaller('lg')
export const isMobile = breakpoints.smaller('sm')

this is closely related to vue's response system
you don't need to worry about memory leaks when using compiler tools like nuxi
however here another problem is that react system cannot determine the dependencies and when to unmount. if you want to declare once and use globally use pinia otherwise use this code:
import { breakpointsTailwind, useBreakpoints } from '#vueuse/core'
export function useMedia() {
const breakpoints = useBreakpoints(breakpointsTailwind)
const isDesktop = breakpoints.greaterOrEqual('lg')
const isTablet = breakpoints.greaterOrEqual('sm') && breakpoints.smaller('lg')
const isMobile = breakpoints.smaller('sm')
return { isDesktop, isTable, isMobile }
}
and use
const { isDesktop } = useMedia()
note: your code doesn't react when changing the values. if you need response use computed

Related

Pinia: $reset alternative when using setup syntax

I have a pinia store created with setup syntax like:
defineStore('id', () => {
const counter = ref(0)
return { counter }
})
Everything has been working great with setup syntax because I can re-use other pinia stores.
Now, however, I see the need to re-use Pinia stores on other pages but their state needs to be reset.
In Vuex for example, I was using registerModule and unregisterModule to achieve having a fresh store.
So the question is: How to reset the pinia store with setup syntax?
Note: The $reset() method is only implemented for stores defined with the object syntax, so that is not an option.
Note 2: I know that I can do it manually by creating a function where you set all the state values to their initial ones
Note 3: I found $dispose but it doesn't work. If $dispose is the answer, then how it works resetting the store between 2 components?
You can use a Pinia plugin that adds a $reset() function to all stores:
On the Pinia instance, call use() with a function that receives a store property. This function is a Pinia plugin.
Deep-copy store.$state as the initial state. A utility like lodash.clonedeep is recommended for state that includes nested properties or complex data types, such as Set.
Define store.$reset() as a function that calls store.$patch() with a deep-clone of the initial state from above. It's important to deep-clone the state again in order to remove references to the copy itself.
// store.js
import { createPinia } from 'pinia'
import cloneDeep from 'lodash.clonedeep'
const store = createPinia()
1️⃣
store.use(({ store }) => {
2️⃣
const initialState = cloneDeep(store.$state)
3️⃣
store.$reset = () => store.$patch(cloneDeep(initialState))
})
demo
Feel free to read the article based on this answer: How to reset stores created with function/setup syntax
You can do this as suggested in the documentation here
myStore.$dispose()
const pinia = usePinia()
delete pinia.state.value[myStore.$id]

Return vue component from a function

I'm new to Vue and I would want to render an SVG icon depending on task status and would like to create a re-usable function for that, How can I do that?
In React I could have done something like this:
const iconStatusMapping = {
todo: <svg></svg>,
processing: <svg>...</svg>,
done: <svg>...</svg>
}
// utils.ts
export const getTaskStatusIcon = (status: TaskStatus) => {
return iconStatusMapping[status]
}
function App() {
const status = "todo"
return (
<div>{getTaskStatusIcon(status)} {status}</div>
)
}
How can I do something similar in Vue3?
In React, it may be not a good idea to define reused elements as JSX that is not wrapped in a function because element objects are expected to be new on every render. This may have no consequences for SVG icons but may have unexpected behaviour in other cases.
In Vue, this snippet could be directly translated to render function and JSX.
Static HTML like SVG icons can be safely defined as strings and outputted with Vue v-html, the same applies to React dangerouslySetInnerHTML:
const iconStatusMapping = {
todo: `<svg></svg>`,
...
}
and
v-html="iconStatusMapping[status]"

Globally Accessible Component Instance

In our production applications with Vue 2.x, we have a toast component. This toast component is mounted once via a plugin (code below) and is then added to the Vue prototype making it accessible in every component instance.
This makes life a lot easier instead of having to add the toast to everywhere we use.
Vue 2.x plugin
export default {
install(vue: any, _: any) {
const root = new Vue({ render: (createElement) => createElement(Toast) });
root.$mount(document.body.appendChild(document.createElement("div")));
const toastInstance: Toast = root.$children[0] as Toast;
vue.prototype.$toast = {
show: (state: ToastState, text: string) => { toastInstance.show(state, text); },
hide: () => { toastInstance.hide(); }
};
}
Which can then be called in any component like:
this.$toast.show(ToastStates.SUCCESS, "Some success message");
I have recently started another project and would like to do something similar, except using Vue 3. Because we don't have access to this in the setup function, I can't use the same approach as before.
I have been looking into a few things, and have found a few ways of doing it, but none as a definitive best practice.
Provide / Inject:
This seems the most promising, where I can use
export const appInstance = createApp(App);
then
appInstance.provide("toast", toastComponentInstance)
which I can then inject in any components. The problem with this, is that to get it available in every component, it needs to be attached to the initial app instance, where it hasn't been created yet. Maybe I could manually mount it and pass it in (but that seems like a hack).
Composition:
I have also looked at this issue here: How to access root context from a composition function in Vue Composition API / Vue 3.0 + TypeScript? but didn't find that very useful and I had to do all types of hacks to actually gain access to the plugin. Gross code below..
export function useToast() {
const root = getCurrentInstance();
const openToast: (options: ToastOptions) => void = (options: ToastOptions) => {
root.ctz.$toast.open(options);
}
const closeToast: () => void = () => {
root.ctx.$toast.close();
}
return {
openToast,
closeToast
}
}
I have other ideas but they seem far fetched an hacky. Keen to hear peoples thoughts on other solutions. I just want a simple way to have 1 instance of a toast, that I can call two functions on to open / close it when and where I want.
This is roughly how I'd do it...
I'd use Composition API, because it makes passing around internals easy
(I'm using popup instead of toast for simplicity)
myPopup.vue
// internal
const popupMessage = Vue.ref('');
const popupVisible = Vue.ref(true);
// external
export const popUpShow = function(message) {
popupMessage.value = message
popupVisible.value = true
}
export const popupHide = function () {
popupVisible.value = false
}
export default {
setup(){
return {
popupMessage, popupVisible, popupHide
}
}
}
Some component, anywhere, composition or class based...
import { popUpShow } from "./myPopup";
export default {
methods: {
myTriggeredEvent() {
popUpShow("I am your Liter")
}
}
}
By exposing popUpShow, which acts as a singleton, you can import that from anywhere, and not have to worry about context.
There the drawback in using this kind of setup/architecture is that it doesn't scale well. The problem happens if your architecture reaches a certain size, and you have multiple triggers coming from various sources that the component needs to have complex logic to handle its state (not likely for this example though). In that case, a managed global store, ie. Vuex, might be a better choice.

Vuetify override default prop value

Is there any way to change default value of a prop in a vuetify component?
For example lets say we have a component like v-btn.
This component has many props, One of them like outlined with default value of false.
Lets say i want is to change this default value to true forever in my application. Is there any way?
I was able to do that at the top of my app's entry point (before any Vue component creation).
/**
* [required imports]
* (you must somehow import VBtn component separately)
*/
Vue.use(Vuetify);
VBtn.options.props.outlined.default = true;
But this practice is called monkey patching and not encouraged to use, consider to use inheritance instead.
In my case I was trying to get component from Vue.options.components['VBtn'] but it didn't work.
So I monkey patched vue library too:
import Vue from "vue";
import Vuetify from 'vuetify'
export const vueComponentsImported: any = {};
export const vueComponentFnDefault = Vue.component.bind(Vue);
/** #see node_modules/vue/src/core/global-api/assets.js */
export const vueComponentFnModded = (id, component) => {
vueComponentsImported[id] = component;
return vueComponentFnDefault(id, component);
};
Vue.component = vueComponentFnModded;
Vue.use(Vuetify);
let VBtn = vueComponentsImported['VBtn'];
if (VBtn) {
VBtn.options.props.outlined.default = true;
}
(please feel free to edit this code if it doesn't work, I have much more lines in my app)
It doesn't make sense to do this,you could just replace '<v-btn' with '<v-btn outlined'.

How to separate production and development code in React Native?

I haven't found any proper answer to this sadly.
I'm currently developing a react-native app with redux and I found out that the release version is getting slowed down by some development tools.
Here's an example:
const store = createStore(
Reducers,
composeWithDevTools(
applyMiddleware(thunk),
),
);
And this composeWithDevTools is obviously some development tool and in release I should use another function called compose.
What I'd like to do would be something like this:
//development
const store = createStore(
Reducers,
composeWithDevTools(
applyMiddleware(thunk),
),
);
//production
const store = createStore(
Reducers,
composeWithDevTools(
applyMiddleware(thunk),
),
);
//end
It would automatically choose the right code sample considering where I am. (Dev or release).
Do you guys know some tool I could use for this ?
Thanks in advance
You can use __DEV__ global constant. Moreover, babel compiler smart enough to completely remove code guarded by constants, so you also can reduce code size.
For example, we use this code to initialize store:
function setupStore(extra = {}) {
const middlewares = [thunk.withExtraArgument(extra)]
if (__DEV__) {
const createLogger = require('redux-logger').createLogger // redux-logger 3.x
const logger = createLogger()
middlewares.push(logger)
}
const store = createStore(reducer, applyMiddleware(...middlewares), autoRehydrate())
return store
}
Redux Logger has been changed a little while ago.
Now you need to import like this:
import { createLogger } from 'redux-logger';
In the example above you can do something like this:
const { createLogger } = require('redux-logger');
const logger = createLogger();
middlewares.push(logger);