vuejs nextick don't update - node.js

i try to connect my frontend to my backend,
the request is done correctly i received the correct data, but the DOM is not updating. I use this.$nextTick but it doesn't affect the update
in the template i use {{ system.CPU.avgload }}
like i said the fetch is done correctly it pass into nexttick, but nothing change
in the main vue i have this
import System from '../utils/system'
import Auth from '../utils/auth'
export default {
created: function () {
this.system = {
CPU: {
avgload: 0
}
}
},
mounted: function () {
this.fetchData()
setInterval(function () {
this.fetchData()
}.bind(this), 10000)
},
methods: {
fetchData () {
if (!Auth.checkAuth) {
console.log('test')
this.error = true
} else {
var self = this
this.$nextTick(function () {
System.Get(function (response) {
self.system = response
})
})
}
}
}
}
and the template is
<div class="text-xs-left" id="example-caption-1">CPU : {{ system.CPU.avgload }} %</div>

You have to add variable system in the data section of vue instance. Than only this variable will become reactive and available in the HTML.
export default {
data: function () {
return { system: {
CPU: {
avgload : ""
}
}
}
}
...
...

Related

How to update Vuex state? (MEVN stack)

I am doing a school project and currently I am just trying out Vuex, I want to retrieve a list of workshops from Vuex, but I can't seem to update my state.
This is my Node backend:
router.get('/all', (req, res) => {
Workshop.find({})
.then( workshop => {
return res.status(201).json({
workshop: workshop,
success: true
})
})
.catch( err => {
console.log(err)
})
})
This is my result in Postman:
This is my Vuex store:
import axios from 'axios'
const state = {
workshop: {}
}
const getters = {
workshop: state => state.workshop
}
const actions = {
async getWorkshop({ commit }) {
let res = await axios.get('http://localhost:5000/api/workshops/all');
commit('workshop_success', res.data.workshop);
return res.data.workshop;
}
};
const mutations = {
workshop_success(state, workshop) {
state.workshop = workshop
}
};
export default {
state,
getters,
actions,
mutations
}
This is my component:
<template>
<p>{{ workshop }}</p>
</template>
<script>
import { mapGetters, mapActions } from 'vuex'
export default {
computed: {
...mapGetters(['workshop'])
},
methods: {
...mapActions(['getWorkshop'])
},
created() {
this.getWorkshop
},
}
</script>
The problem is, I am able to get the workshop state through Vuex, it displays a simple empty object "{}" (which is the initial state), but it seems like I am unable to trigger the action through the created hook, and the state does not change. If anyone has an idea of what I did wrong, that would be really helpful, because I am really lost right now. Thank you in advance!
state is not a state but context object in mutation, etc parameters. Otherwise commit, etc couldn't be accessed.
It is:
workshop_success({ state }, workshop) {
state.workshop = workshop
}
Also this is no-op:
created() {
this.getWorkshop
},
A function should be called like this.getWorkshop().

Using CKEditor in a component with Laravel/Inertia

I'd like to use Ckeditor in my Laravel/Inertia project and i can't get it to work. I found a tutorial from LaraTips, but that was written for VueJS-2. I am working with the lastest version Inertia which uses VueJS-3.
I want to use Ckeditor in a separate component, and it (sort of) works, but i can't get the old data to show in the editor. I get an error "Uncaught (in promise) TypeError: Cannot read property 'setData' of undefined at Proxy.modelValue (app.js:29)"
What am i doing wrong?
This is my component:
<template>
<ckeditor :editor="editor" v-model="text" :config="editorConfig"></ckeditor>
</template>
<script>
import ClassicEditor from '#ckeditor/ckeditor5-build-classic';
export default {
data() {
return {
text: "",
editor: ClassicEditor,
editorConfig: {
// The configuration of the editor.
},
}
},
props: {
modelValue: {}
},
setup() {
},
watch: {
modelValue: {
immediate: true,
handler(modelValue) {
this.text = modelValue;
}
},
text(text) {
this.$emit('update:modelValue', text);
}
},
}
</script>
Any suggestions??
I am doing the same tutorial (i am using vueJS-3).
this may work for you:
in app.js include CKEditor:
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => require(`./Pages/${name}.vue`),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use( CKEditor)
.mixin({ methods: { route } })
.mount(el);
},
});
In Components/CkEditor.vue check what are you emitting
look for this this.$emit("input", text);
<template>
<ckeditor :editor="editor" v-model="text" :config="editorConfig"></ckeditor>
</template>
<script>
import ClasicEditor from "#ckeditor/ckeditor5-build-classic";
export default {
props: {
value: {},
},
data() {
return {
text: "",
editor: ClasicEditor,
editorConfig: {
// The configuration of the editor.
},
};
},
watch: {
value:{
inmediate: true,
handler(value){
this.text = value;
}
},
text(text) {
this.$emit("input", text);
},
},
};
</script>
let me know if that worked for you
I looked at the answer here, and below is what worked for me:
Hope this helps! :)
I am using laravel/inertia with vue 3.
app.js
import './bootstrap';
import '../css/app.css';
import { createApp, h } from 'vue';
import { createInertiaApp } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
import { createPinia } from 'pinia';
import { _t } from './Utilities/translations';
import CKEditor from '#ckeditor/ckeditor5-vue';
const appName =
window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) =>
resolvePageComponent(
`./Pages/${name}.vue`,
import.meta.glob('./Pages/**/*.vue')
),
setup({ el, app, props, plugin }) {
const vue_app = createApp({ render: () => h(app, props) });
vue_app.use(plugin);
vue_app.use(ZiggyVue, Ziggy);
vue_app.use(createPinia());
// Register all base components globally
const components = import.meta.globEager('./Components/Base/*.vue');
for (const path in components) {
let componentName;
if (path.split) {
const split_componentName = path.split('/').pop();
if (split_componentName) {
componentName = split_componentName.replace(/\.\w+$/, '');
vue_app.component(componentName, components[path].default);
}
}
}
vue_app.config.globalProperties.$_t = _t;
vue_app.use(CKEditor);
vue_app.mount(el);
return vue_app;
}
});
InertiaProgress.init({ color: '#4B5563' });
CKEditor Component:
<template>
<div id="app">
<ckeditor
v-model="editor_data"
:editor="editor"
:config="editor_config"
></ckeditor>
</div>
</template>
<script setup lang="ts">
import { reactive, ref } from '#vue/reactivity';
import * as editor from '#ckeditor/ckeditor5-build-classic';
const editor_data = ref('');
const editor_config = {};
</script>

Use Worker output in a vue component

I tried to send the output from my worker to my component.vue by window.localStorage.
Does anybody know how to show and update my worker's result in my component vue automatically?
This is my code:
worker-api.js
import Worker from "worker-loader!./worker.js";
const worker = new Worker();
worker.addEventListener('message', (e) => {
window.localStorage.setItem('result', JSON.stringify(e.data));
});
export function sendMessage(msg) {
worker.postMessage(msg);
}
worker.js
self.addEventListener("message", (e) => {
var count = e.data;
while(count < 20) {
const result = e.data + 3
self.postMessage(result);
}
});
my-component.vue
<template>
<p>Count: "{{ result }}"</p>
</template>
<script>
import Button from './Button'
import { sendMessage } from './worker-api'
export default {
name: 'my-component',
components: {Button},
data () {
return {
count : 0
}
},
computed: {
result: function () {
return JSON.parse(window.localStorage.getItem('result'))
}
},
methods: {,
postMessage() {
sendMessage(this.count)
}
},
}
</script>
It is not possible to deal with localStorage values as if they were reactive. Probably, that's why your computed property does not work.
One possible solution is to import your worker inside your component and use to update a reactive variable.
Something similar to:
component.vue
<template>
<button #click="increment">Increment Result</button>
{{ result }}
</template>
<script>
export default {
data() {
return {
// the worker path must be relative to the /public folder (in this example, the worker.js file must be at /public/worker.js)
worker: new Worker('/worker.js'),
result: 0
}
},
created() {
const self = this
this.worker.onmessage = function(event) {
self.result = event.data
}
},
methods: {
increment() {
this.worker.postMessage(this.result)
}
}
}
</script>
/public/worker.js
onmessage = function(event) {
// data sent by the Vue component is retrieved from 'data' attribute
postMessage(event.data + 1)
}

next-i18next Jest Testing with useTranslation

Testing libs...always fun. I am using next-i18next within my NextJS project. We are using the useTranslation hook with namespaces.
When I run my test there is a warning:
console.warn
react-i18next:: You will need to pass in an i18next instance by using initReactI18next
> 33 | const { t } = useTranslation(['common', 'account']);
| ^
I have tried the setup from the react-i18next test examples without success. I have tried this suggestion too.
as well as just trying to mock useTranslation without success.
Is there a more straightforward solution to avoid this warning? The test passes FWIW...
test('feature displays error', async () => {
const { findByTestId, findByRole } = render(
<I18nextProvider i18n={i18n}>
<InviteCollectEmails onSubmit={jest.fn()} />
</I18nextProvider>,
{
query: {
orgId: 666,
},
}
);
const submitBtn = await findByRole('button', {
name: 'account:organization.invite.copyLink',
});
fireEvent.click(submitBtn);
await findByTestId('loader');
const alert = await findByRole('alert');
within(alert).getByText('failed attempt');
});
Last, is there a way to have the translated plain text be the outcome, instead of the namespaced: account:account:organization.invite.copyLink?
Use the following snippet before the describe block OR in beforeEach() to mock the needful.
jest.mock("react-i18next", () => ({
useTranslation: () => ({ t: key => key }),
}));
Hope this helps. Peace.
use this for replace render function.
import { render, screen } from '#testing-library/react'
import DarkModeToggleBtn from '../../components/layout/DarkModeToggleBtn'
import { appWithTranslation } from 'next-i18next'
import { NextRouter } from 'next/router'
jest.mock('react-i18next', () => ({
I18nextProvider: jest.fn(),
__esmodule: true,
}))
const createProps = (locale = 'en', router: Partial<NextRouter> = {}) => ({
pageProps: {
_nextI18Next: {
initialLocale: locale,
userConfig: {
i18n: {
defaultLocale: 'en',
locales: ['en', 'fr'],
},
},
},
} as any,
router: {
locale: locale,
route: '/',
...router,
},
} as any)
const Component = appWithTranslation(() => <DarkModeToggleBtn />)
const defaultRenderProps = createProps()
const renderComponent = (props = defaultRenderProps) => render(
<Component {...props} />
)
describe('', () => {
it('', () => {
renderComponent()
expect(screen.getByRole("button")).toHaveTextContent("")
})
})
I used a little bit more sophisticated approach than mocking to ensure all the functions work the same both in testing and production environment.
First, I create a testing environment:
// testing/env.ts
import i18next, { i18n } from "i18next";
import JSDomEnvironment from "jest-environment-jsdom";
import { initReactI18next } from "react-i18next";
declare global {
var i18nInstance: i18n;
}
export default class extends JSDomEnvironment {
async setup() {
await super.setup();
/* The important part start */
const i18nInstance = i18next.createInstance();
await i18nInstance.use(initReactI18next).init({
lng: "cimode",
resources: {},
});
this.global.i18nInstance = i18nInstance;
/* The important part end */
}
}
I add this environment in jest.config.ts:
// jest.config.ts
export default {
// ...
testEnvironment: "testing/env.ts",
};
Sample component:
// component.tsx
import { useTranslation } from "next-i18next";
export const Component = () => {
const { t } = useTranslation();
return <div>{t('foo')}</div>
}
And later on I use it in tests:
// component.test.tsx
import { setI18n } from "react-i18next";
import { create, act, ReactTestRenderer } from "react-test-renderer";
import { Component } from "./component";
it("renders Component", () => {
/* The important part start */
setI18n(global.i18nInstance);
/* The important part end */
let root: ReactTestRenderer;
act(() => {
root = create(<Component />);
});
expect(root.toJSON()).toMatchSnapshot();
});
I figured out how to make the tests work with an instance of i18next using the renderHook function and the useTranslation hook from react-i18next based on the previous answers and some research.
This is the Home component I wanted to test:
import { useTranslation } from 'next-i18next';
const Home = () => {
const { t } = useTranslation("");
return (
<main>
<div>
<h1> {t("welcome", {ns: 'home'})}</h1>
</div>
</main>
)
};
export default Home;
First, we need to create a setup file for jest so we can start an i18n instance and import the translations to the configuration. test/setup.ts
import i18n from "i18next";
import { initReactI18next } from "react-i18next";
import homeES from '#/public/locales/es/home.json';
import homeEN from '#/public/locales/en/home.json';
i18n.use(initReactI18next).init({
lng: "es",
resources: {
en: {
home: homeEN,
},
es: {
home: homeES,
}
},
fallbackLng: "es",
debug: false,
});
export default i18n;
Then we add the setup file to our jest.config.js:
setupFilesAfterEnv: ["<rootDir>/test/setup.ts"]
Now we can try our tests using the I18nextProvider and the useTranslation hook:
import '#testing-library/jest-dom/extend-expect';
import { cleanup, render, renderHook } from '#testing-library/react';
import { act } from 'react-dom/test-utils';
import { I18nextProvider, useTranslation } from 'react-i18next';
import Home from '.';
describe("Index page", (): void => {
afterEach(cleanup);
it("should render properly in Spanish", (): void => {
const t = renderHook(() => useTranslation());
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home / >
</I18nextProvider>
);
expect(component.getByText("Bienvenido a Pocky")).toBeInTheDocument();
});
it("should render properly in English", (): void => {
const t = renderHook(() => useTranslation());
act(() => {
t.result.current.i18n.changeLanguage("en");
});
const component = render(
<I18nextProvider i18n={t.result.current.i18n}>
<Home/>
</I18nextProvider>
);
expect(component.getByText("Welcome to Pocky")).toBeInTheDocument();
});
});
Here we used the I18nextProvider and send the i18n instance using the useTranslation hook. after that the translations were loaded without problems in the Home component.
We can also change the selected language running the changeLanguage() function and test the other translations.

How to access vm.$el properties with single-file components?

I'm trying to access the clientHeightproperty of a vue instance created by a single-file component, but it returns undefined. How can I do this?
<template lang='jade'>
article#article.projectCard.relative.mb5.pa5( v-bind:style="styleObject")
h3 {{ project.projectName }}
p {{ project.projectDescription }}
</template>
<script>
export default {
props: {
project: '',
},
data () {
return {
styleObject: {
backgroundColor: this.project.projectMainColor,
height: '80vh'
},
cardHeight: this.clientHeight,
};
},
</script>
You can access the element after it's mounted with this.$el so you'd actually want this.$el.clientHeight after it's mounted.
You can do like:
data () {
return {
cardHeight: 0,
}
}
Then do:
mounted () {
this.cardHeight = this.$el.clientHeight + 'px'
}
Also, that styleObject would be better as a computed property. That way as things change it'll be automatically updated.
I'd personally do:
data () {
return {
cardHeight: '80vh',
}
},
mounted () {
this.cardHeight = this.$el.clientHeight + 'px'
},
computed: {
styleObject () {
return {
backgroundColor: this.project.projectMainColor,
height: this.cardHeight,
}
}
}

Resources