Related
I created a server-side rendered Vue.js blog using Nuxt.js with Typescript and Ghost but I'm having some issues to update html metatag using data from asyncData().
From Nuxt.js documentation I know that asyncData() is called every time before loading page components and merges with component data.
I'm getting this error:
Property 'title' does not exist on type '{ asyncData({ app, params }: Context): Promise<{ title: string | undefined; excerpt: string | undefined; feature_image: Nullable | undefined; html: Nullable | undefined; }>; head(): any; }'.
This is my code:
<script lang="ts">
import { Context } from '#nuxt/types'
import { PostOrPage } from 'tryghost__content-api'
export default {
async asyncData ({ app, params }: Context) {
const post: PostOrPage = await app.$ghost.posts.read(
{
slug: params.slug
},
{ include: 'tags' }
)
return {
title: post.title,
excerpt: post.excerpt,
feature_image: post.feature_image,
html: post.html
}
},
head () {
return {
title: this.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.excerpt
}
]
}
}
}
</script>
I already tried some solutions like using data() to set a default value for each item but nothing. Do you have any suggestion?
Without a typescript plugin like nuxt-property-decorator you won't have Typescript support for nuxt (either way, type checking and autocomplete still won't work).
That's why asyncData & fetch should be in Component options.
#Component({
asyncData (ctx) {
...
}
})
export default class YourClass extends Vue {
...
}
instead of
#Component
export default class YourClass extends Vue {
asyncData (ctx) {
...
}
}
If you still want to use asyncData() inside of your component class instead of setting the option, see this working example using the npm module nuxt-property-decorator.
Here is the working code after implementing the suggestion from #nonNumericalFloat :
import { Component, Vue } from 'nuxt-property-decorator'
import { Context } from '#nuxt/types'
import { PostOrPage } from 'tryghost__content-api'
import Title from '~/components/Title.vue'
#Component({
components: {
Title
}
})
export default class Page extends Vue {
post!: PostOrPage
async asyncData ({ app, params }: Context) {
const post: PostOrPage = await app.$ghost.posts.read(
{
slug: params.slug
},
{ include: 'tags' }
)
return {
post
}
}
head () {
return {
title: this.post.title,
meta: [
{
hid: 'description',
name: 'description',
content: this.post.excerpt
}
]
}
}
}
I have a <form> in vue. I send that form to server, get a JSON response, print it to console. It works fine.
However I need to take that JSON response and display it on another page. For instance, I have two .vue files: GetAnimal.vue that has the form and retrieves the animal data from an API and a DisplayAnimal.vue that displays animal's data. I need to direct the response animal data from GetAnimal.vue to DisplayAnimal.vue.
GetAnimal.vue:
<template>
<form v-on:submit.prevent="getAnimal()">
<textarea v-model = "animal"
name = "animal" type="animal" id = "animal"
placeholder="Enter your animal here">
</textarea>
<button class = "custom-button dark-button"
type="submit">Get animal</button>
</form>
</template>
<script>
import axios from 'axios';
export default {
name: 'App',
data: function() {
return {
info: '',
animal: ''
}
},
methods: {
getAnimal: function() {
axios
.get('http://localhost:8088/animalsapi?animal=' + this.animal)
.then(response => (this.info = response.data));
console.log(this.info);
}
}
}
</script>
response:
retrieves a JSON with animal data, say like this:
{
"fur-color": "yellow",
"population": 51000,
"isExtinct": false,
"isDomesticated": true
}
and I now want to give that JSON to a DisplayAnimal.vue at /viewanimal endpoint:
DisplayAnimal.vue:
<template>
<div>
<p>Animal name: {{animal}}}</p>
<p>Fur color: {{furColor}}</p>
<p>Population: {{population}}</p>
<p>Is extinct: {{isExtinct}}</p>
<p>Is domesticated: {{isDomesticated}}</p>
</div>
</template>
How would I do that? I know I can redirect via this.$router.push({ path });, but I've only used it for navigation, while here JSON response needs to be passed. Is this even a correct / good practice way of approaching this?
EDIT:
I tried this:
in GetAnimal.vue I added this data:
data: function() {
return {
animal: {
name: 'Cat',
furColor: 'red',
population: '10000',
isExtinct: false,
isDomesticated: true
}
and in DisplayAnimal.vue this:
<script>
export default {
props: {
animal: {
name: {
type: String
},
furColor: {
type: String
},
population: String,
isExtinct: String,
isDomesticated: String
}
}
}
</script>
and in GetAnimal.vue I added this:
methods: {
animals: function() {
alert("animals");
this.$router.push({name: 'viewanimal',
query: {animal: JSON.stringify(this.animal)}});
},
to try to display that test animal using the display component. However it just didn't work - I get an empty page.
Using Vuex, you can solve this easily
Working example on netlify
https://m-animalfarm.netlify.app/
code on github
https://github.com/manojkmishra/animalfarm
GetAnimal.vue ( I have disabled axios call for testing and hardcoded info)
<template>
<form v-on:submit.prevent="getAnimal()">
<textarea v-model = "animal" name = "animal" type="animal" id = "animal"
placeholder="Enter your animal here">
</textarea>
<button class = "custom-button dark-button"
type="submit">Get animal</button>
</form>
</template>
<script>
import axios from 'axios';
export default
{
name: 'App',
data: function() { return { info: '', animal: '' } },
methods: {
getAnimal: function() {
// axios
// .get('http://localhost:8088/animalsapi?animal=' + this.animal)
// .then(response => (this.info = response.data),
this.info={"fur-color": "yellow","population": 51000,"isExtinct":
false,"isDomesticated": true },
this.$store.dispatch('storeAnimals', this.info)
//);
}
}
}
</script>
DisplayAnimal.vue
<template>
<div>
<p>Animal name: {{stateAnimal.animal}}</p>
<p>Fur color: {{stateAnimal.furColor}}</p>
<p>Population: {{stateAnimal.population}}</p>
<p>Is extinct: {{stateAnimal.isExtinct}}</p>
<p>Is domesticated: {{stateAnimal.isDomesticated}}</p>
</div>
</template>
<script>
import {mapState, mapGetters} from 'vuex';
export default {
computed:{ ...mapState({ stateAnimal:state => state.modulename.stateAnimal }),
},
}
</script>
modulename.js ( store module)
export default
{
state: {stateAnimal:null, },
getters:{ },
mutations:
{ ['STORE_ANIMALS'] (state, payload)
{ state.stateAnimal = payload;
console.log('state=',state)
},
},
actions:
{ storeAnimals: ({commit}, data) =>
{ console.log('storeanim-data-',data);
commit( 'STORE_ANIMALS', data );
},
}
}
Index.js (for vuex store), you can disable persistedstate as its for saving state if page is refreshed
import Vue from 'vue'
import Vuex from 'vuex'
import modulename from './modules/modulename'
import createPersistedState from "vuex-persistedstate";
Vue.use(Vuex)
export default new Vuex.Store({
plugins: [createPersistedState({ storage: sessionStorage })],
state: { },
mutations: { },
actions: { },
modules: { modulename }
})
State is available/shared for all the components
well first of all create a second folder call it services and create service.js for you axios call- good practice and cleaner code overall.
second use vuex. this kind of data is best used with vuex.
As far as I understand GetAnimal.vue is the parent component and you wish to display it in the child component DisplayAnimal.vue.
If so and you wish to see if this works just use props.
you can also send that same information or any other information for the child back to the parent using an $emit().
STRONGLY recommended to use vuex in order to manage the state
Vue.component('product',{
props:{
message:{
type:String,
required:true,
default:'Hi.'
}
},
template:`<div>{{message}}</div>`,
data(){...}
})
//html in the other component you axios call is in this component //<product meesage="hello"></product>
I would pass the animal name/id as a route param to the display page and have that component responsible for fetching and displaying the relevant animal data. This avoids the situation where a user could visit the display page directly via the URL and see an incomplete page.
In situations where you want to share local state between pages, as others have pointed out you'd probably want to use Vuex.
EDIT:
I'm adding some code to my answer as requested by the OP.
Routes:
const routes = [
{ path: "/", component: SearchAnimals },
{ path: "/viewanimal/:name", component: DisplayAnimal, name: "displayAnimal" }
];
DisplayAnimal.vue:
<template>
<div>
<p>Animal name: {{animal.name}}</p>
<p>Fur color: {{animal.furColor}}</p>
<p>Population: {{animal.population}}</p>
<p>Is extinct: {{animal.isExtinct}}</p>
<p>Is domesticated: {{animal.isDomesticated}}</p>
</div>
</template>
<script>
import axios from "axios";
export default {
name: "DisplayAnimal",
data: () => ({
animal: {}
}),
methods: {
fetchAnimal(name) {
axios
.get(`http://localhost:8088/animalsapi?animal=${name}`)
.then(response => {
this.animal = response.data;
});
}
},
created() {
this.fetchAnimal(this.$route.params.name);
}
};
</script>
SearchAnimals.vue:
<template>
<form v-on:submit.prevent="onSubmit">
<textarea
v-model="animal"
name="animal"
type="animal"
id="animal"
placeholder="Enter your animal here"
></textarea>
<button type="submit">Get animal</button>
</form>
</template>
<script>
export default {
name: "SearchAnimal",
data: () => ({
animal: ""
}),
methods: {
onSubmit() {
this.$router.push({
name: "displayAnimal",
params: { name: this.animal }
});
}
}
};
</script>
Obviously this is a bare-bones example, so doesn't contain any error handling etc., but it should get you up and running.
I've used vue create to setup a new Vue project, and have installed Storybook - all working correctly.
I have then installed storybook-addon-designs and followed the readme on adding to my story, but it gives me the following error in my console: h is not defined.
Here's my files:
stories/2-PageTitle.stories.js:
import { withDesign } from 'storybook-addon-designs'
import {Button} from '../src/components/Button'
export default {
title: 'My stories',
component: Button,
decorators: [withDesign]
}
export const myStory = () => <Button>Hello, World!</Button>
myStory.story = {
name: 'My awesome story',
parameters: {
design: {
type: 'figma',
url: 'https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931/Sample-File'
}
}
}
babel.config.js:
module.exports = {
presets: [
'#vue/cli-plugin-babel/preset'
]
}
.storybook/main.js:
module.exports = {
stories: ['../stories/**/*.stories.js'],
addons: ['storybook-addon-designs']
};
src/components/Button.vue:
<template>
<button>
{{ label }}
</button>
</template>
<script>
export default {
name: 'Button',
props: {
label: String
}
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>
button {
background: red;
}
</style>
Can anyone see what I'm doing wrong here?
Full code here (I'd of done a Sandbox but because it uses Storybook this seems like a better way?): https://github.com/A7DC/storybookvueaddonstest
The author of storybook-addon-designs suggests the following:
You have to replace the export
const myStory = () => <Button>Hello, World!</Button>
You need to change this line (React story) to Vue's one. For example,
export const myStory = () => ({
components: { Button },
template: '<Button>Hello, World!</Button>'
})
Updated answer -
import { withDesign } from "storybook-addon-designs";
import Button from "../src/components/Button";
export default {
title: "My Stories",
decorators: [withDesign],
};
export const myStory = () => ({
components: { Button },
template: "<Button> Hello, World!</Button >",
});
myStory.story = {
name: "My awesome story",
parameters: {
design: {
type: "figma",
url: "https://www.figma.com/file/LKQ4FJ4bTnCSjedbRpk931/Sample-File",
},
},
};
I have app where user can login in different roles, eg. seller, buyer and admin.
For each user I'd like to show dashboard page on the same path, eg. http://localhost:8080/dashboard
However, each user will have different dashboard defined in different vue components, eg. SellerDashboard, BuyerDashboard and AdminDashboard.
So basically, when user opens http://localhost:8080/dashboard vue app should load different component based on the user role (which I store in vuex). Similarly, I'd like to have this for other routes. For example, when user goes to profile page http://localhost:8080/profile app should show different profile component depending on the logged in user.
So I'd like to have the same route for all users roles as opposed to have different route for each user role, eg. I don't want user role to be contained in url like following: http://localhost:8080/admin/profile and http://localhost:8080/seller/profile etc...
How can I implement this scenario with vue router?
I tried using combination of children routes and per-route guard beforeEnter to resolve to a route based on user role. Here is a code sample of that:
in router.js:
import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import store from '#/store'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'home',
component: Home,
beforeEnter: (to, from, next) => {
next({ name: store.state.userRole })
},
children: [
{
path: '',
name: 'admin',
component: () => import('#/components/Admin/AdminDashboard')
},
{
path: '',
name: 'seller',
component: () => import('#/components/Seller/SellerDashboard')
},
{
path: '',
name: 'buyer',
component: () => import('#/components/Buyer/BuyerDashboard')
}
]
},
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
in store.js:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
userRole: 'seller' // can also be 'buyer' or 'admin'
}
})
App.vue contains parent router-view for top-level routes, eg. map / to Home component and /about to About component:
<template>
<router-view/>
</template>
<script>
export default {
name: 'App',
}
</script>
And Home.vue contains nested router-view for different user's role-based components:
<template>
<div class="home fill-height" style="background: #ddd;">
<h1>Home.vue</h1>
<!-- nested router-view where user specific component should be rendered -->
<router-view style="background: #eee" />
</div>
</template>
<script>
export default {
name: 'home'
}
</script>
But it doesn't work because I get Maximum call stack size exceeded exception in browser console when I call next({ name: store.state.userRole }) in beforeEnter. The exception is:
vue-router.esm.js?8c4f:2079 RangeError: Maximum call stack size exceeded
at VueRouter.match (vue-router.esm.js?8c4f:2689)
at HTML5History.transitionTo (vue-router.esm.js?8c4f:2033)
at HTML5History.push (vue-router.esm.js?8c4f:2365)
at eval (vue-router.esm.js?8c4f:2135)
at beforeEnter (index.js?a18c:41)
at iterator (vue-router.esm.js?8c4f:2120)
at step (vue-router.esm.js?8c4f:1846)
at runQueue (vue-router.esm.js?8c4f:1854)
at HTML5History.confirmTransition (vue-router.esm.js?8c4f:2147)
at HTML5History.transitionTo (vue-router.esm.js?8c4f:2034)
and thus nothing is rendered.
Is there a way I can solve this?
You might want to try something around this solution:
<template>
<component :is="compName">
</template>
data: () {
return {
role: 'seller' //insert role here - maybe on `created()` or wherever
}
},
components: {
seller: () => import('/components/seller'),
admin: () => import('/components/admin'),
buyer: () => import('/components/buyer'),
}
Or if you prefer maybe a bit more neat (same result) :
<template>
<component :is="loadComp">
</template>
data: () => ({compName: 'seller'}),
computed: {
loadComp () {
const compName = this.compName
return () => import(`/components/${compName}`)
}
}
This will give you the use of dynamic components without having to import all of the cmps up front, but using only the one needed every time.
Such code retrieves component code only for a given role:
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import store from "../store";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "home",
component: () => {
switch (store.state.userRole) {
case "admin":
return import("../components/AdminDashboard");
case "buyer":
return import("../components/BuyerDashboard");
case "seller":
return import("../components/SellerDashboard");
default:
return Home;
}
}
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
One approach would be to use a dynamic component. You could have a single child route whose component is also non-specific (e.g. DashboardComponent):
router.js
const routes = [
{
path: '/',
name: 'home',
children: [
{
path: '',
name: 'dashboard',
component: () => import('#/components/Dashboard')
}
]
}
]
components/Dashboard.vue
<template>
<!-- wherever your component goes in the layout -->
<component :is="dashboardComponent"></component>
</template>
<script>
import AdminDashboard from '#/components/Admin/AdminDashboard'
import SellerDashboard from '#/components/Seller/SellerDashboard'
import BuyerDashboard from '#/components/Buyer/BuyerDashboard'
const RoleDashboardMapping = {
admin: AdminDashboard,
seller: SellerDashboard,
buyer: BuyerDashboard
}
export default {
data () {
return {
dashboardComponent: RoleDashboardMapping[this.$store.state.userRole]
}
}
}
</script>
You run into the Maximum call stack size exceeded exception because the next({ name: store.state.userRole }) will trigger another redirection and call the beforeEnter again and thus results in infinite loop.
To solve this, you can check on the to param, and if it is already set, you can call next() to confirm the navigation, and it will not cause re-direction. See code below:
beforeEnter: (to, from, next) => {
// Helper to inspect the params.
console.log("to", to, "from", from)
// this is just an example, in your case, you may need
// to verify the value of `to.name` is not 'home' etc.
if (to.name) {
next();
} else {
next({ name: store.state.userRole })
}
},
I faced the same problem (I use Meteor JS with Vue JS) and I found the way to do it with the render function to load different components on the same route. So, in your case it should be:
import Vue from "vue";
import VueRouter from "vue-router";
import Home from "../views/Home.vue";
import AdminDashboard from "../components/AdminDashboard";
import BuyerDashboard from "../components/BuyerDashboard";
import SellerDashboard from "../components/SellerDashboard";
import store from "../store";
Vue.use(VueRouter);
const routes = [
{
path: "/",
name: "home",
component: {
render: (h) => {
switch (store.state.userRole) {
case "admin":
return h(AdminDashboard);
case "buyer":
return h(BuyerDashboard);
case "seller":
return h(SellerDashboard);
default:
return h(Home);
}
}
}
}
];
const router = new VueRouter({
mode: "history",
base: process.env.BASE_URL,
routes
});
export default router;
Note that this solution also works but only for the first time, if you enter again to that route, the last component loaded it will keep (you will need to reload the page). So, with the render function it always load the new component.
Vue Router 4 (Vue 3)
If you are using Vue Router 4 (usable only with Vue 3), one alternative solution is to use dynamic routing
This new feature allows us to remove/add routes on the fly.
// router.js
import { createRouter, createWebHistory } from 'vue-router'
import store from "../store";
import Home from "../views/Home.vue";
import About from "../views/About.vue";
// all routes independent of user role
const staticRoutes = [
{
path: "/",
name: "home",
component: Home,
},
{
path: "/about",
name: "about",
component: About,
},
]
const getRoutesForRole = (role) => {
// imlementation can vary - see the rest of the answer
}
// routes used at app initialization
const initialRoutes = [...staticRoutes, ...getRoutesForRole(store.state.userRole)]
const router = createRouter({
history: createWebHistory(),
routes: initialRoutes,
})
export default router
export const updateRoutesForRole = () {
// implementation can vary - see the rest of the answer
}
How to generate dynamic routes - getRoutesForRole
The implementation of course depends on many factors - how many routes (and also roles) do you have is probably most important.
With just 2 or 3 routes (and not many roles) it is just fine to use a static definition:
const routesPerRole = {
"admin": [
{
path: "/dashboard",
name: "dashboard",
component: () => import("../components/AdminDashboard.vue")
}, // more routes follow....
],
"seller": [
{
path: "/dashboard",
name: "dashboard",
component: () => import("../components/SellerDashboard.vue")
}, // more routes follow....
],
"buyer": [
{
path: "/dashboard",
name: "dashboard",
component: () => import("../components/BuyerDashboard.vue")
}, // more routes follow....
],
}
const getRoutesForRole = (role) => {
if(!role) return []
return routesPerRole[role]
}
If you have many routes and/or many roles, you probably want something more generic. First we need some good naming convention - for example lets say that we will organize our components in a directory structure like this: #/components/${role}/${componentName}.vue
Then we can use Webpacks dynamic import
const routeTemplates = [
{
path: "/dashboard",
name: "dashboard",
component: 'Dashboard'
},
]
const getRoutesForRole = (role) => {
if(!role) return []
const routesForRole = routeTemplates.map(route => ({
...route,
component: () => import(`#/components/${role}/${route.component}.vue`)
}))
return routesForRole
}
Note that thanks to how import() with dynamic expression works in Webpack this will make Webpack to create new JS chunk for each component in #/components folder which may be not what you want.
Easy fix is to move the "role dependent" components into it's own subfolder so instead of using #/components/admin/.... just use #/components/perRoleComponents/admin/.... and
import(`#/components/perRoleComponents/${role}/${route.component}.vue`)
Other solution is to use different import() statement for each role. This will also allow us to use Webpacks "magic comments" and for example force Webpack to pack all components for each role into single js chunk:
const routeTemplates = [
{
path: "/dashboard",
name: "dashboard",
component: 'Dashboard'
},
]
const getComponentLoader = (role, componentName) => {
switch(role) {
"admin": return () => import(
/* webpackChunkName: "admin-components" */
/* webpackMode: "lazy-once" */
`#/components/admin/${componentName}.vue`)
"seller": return () => import(
/* webpackChunkName: "seller-components" */
/* webpackMode: "lazy-once" */
`#/components/seller/${componentName}.vue`)
"buyer": return () => import(
/* webpackChunkName: "buyer-components" */
/* webpackMode: "lazy-once" */
`#/components/buyer/${componentName}.vue`)
}
}
const getRoutesForRole = (role) => {
if(!role) return []
const routesForRole = routeTemplates.map(route => ({
...route,
component: getComponentLoader(role, route.component)
}))
return routesForRole
}
How to update routes - updateRoutesForRole()
Easiest scenario is when each role has same set of routes and just wants to use a different component. In this case to switch the routes when role changes we can just use addRoute
Add a new route record to the router. If the route has a name and there is already an existing one with the same one, it removes it first.
export const updateRoutesForRole = () {
const role = store.state.userRole
const routesForRole = getRoutesForRole(role)
routesForRole.forEach(r => router.addRoute(r))
}
For more complicated scenarios where not all routes are available for all roles, previous routes (for previous active role - if any) must be removed 1st using removeRoute function. Also our getRoutesForRole() must be different. One solution is to use route meta fields
const routeTemplates = [
{
path: "/dashboard",
name: "dashboard",
component: 'Dashboard',
meta: { forRoles: ['admin', 'seller'] }
},
]
const getRoutesForRole = (role) => {
if(!role) return []
const routesForRole = routeTemplates
.filter(route => route.meta?.forRoles?.includes(role))
.map(route => ({
...route,
component: () => import(`#/components/${role}/${route.component}.vue`)
}))
return routesForRole
}
export const updateRoutesForRole = () {
const role = store.state.userRole
// delete previous 1st
router.getRoutes()
.filter(route => route.meta?.forRoles)
.forEach(route => router.removeRoute(route.name))
const routesForRole = getRoutesForRole(role)
routesForRole.forEach(r => router.addRoute(r))
}
Router v3 (for Vue 2)
Note that Router v3 (and earlier) was never designed with dynamic routing in mind. There is no removeRoute() function. There is a addRoute() so some of the scenarios described above could be probably possible but it currently (Router v3.5.3) does not work as described in the documentation
One way to solve this is to create three separate components DashboardForAdmin, DashBoardForSeller, and DashBoardForBuyer for three types of users.
Then use a mixin.js
export default {
data: function () {
return {
userType : "buyer"; // replace this with a function that returns "seller", "buyer", or "admin"
}
}
}
Create a Vue component DashboardContainer renders the correct dashboard component based on mixin return value
<template>
<div>
<div v-if="userType === 'admin'">
<DashboardForAdmin />
</div>
<div v-else-if="userType === 'buyer'">
<DashboardForBuyer />
</div>
<div v-else>
<DashboardForSeller />
</div>
</div>
</template>
<script>
import mixin from '#/mixin.js';
import DashboardForAdmin from '#/components/DashboardForAdmin.vue';
import DashBoardForSeller from '#/components/DashBoardForSeller.vue';
import DashBoardForBuyer from '#/components/DashBoardForBuyer.vue';
export default {
mixins: [mixin],
components: {
DashboardForAdmin, DashBoardForSeller, DashBoardForBuyer
},
};
</script>
Now you can add a single route for the DashboardContainer
My _nav.js file:
import i18n from '../../services/Translator';
export default {
items: [
{
name: i18n.t('DASHBOARD'),
url: '/dashboard',
icon: 'icon-speedometer',
},
{
name: i18n.t('SCHEDULE'),
url: '/schedule',
icon: 'icon-calendar',
},
{
name: i18n.t('USERS'),
url: '/users',
icon: 'icon-user',
},
{
name: i18n.t('LEASING_COMPANY'),
url: '/company',
icon: 'icon-organization',
},
],
};
My component:
import { translate } from 'react-i18next';
import nav from '../Sidebar/_nav';
/...
render() {
const test = nav.items.map((item) => {
return <li key={item.url}>{item.name}</li>;
});
return (
<ul>{test}</ul>
);
}
The problem is I don't get my text translated when I change language. My browser need to be refreshed to apply translation. Anyone know how to get translation done without page refresh?
Edit: This is my Translator service:
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import en from '../../lang/en';
import vn from '../../lang/vn';
import env from '../../config/env';
i18n
.use(LanguageDetector)
.init({
// we init with resources
resources: {
en,
vn,
},
fallbackLng: env.defaultLanguage,
// have a common namespace used around the full app
ns: ['translations'],
defaultNS: 'translations',
keySeparator: false, // we use content as keys
react: {
wait: true,
},
});
export default i18n;
I also put my change language button on the Header component in my page.
Can't work like this...you assign translations to objects in the array inside _nav.js
Those will be strings no way any code will ever update those values you will need to regenerate those on language change or:
import i18n from '../../services/Translator';
export default {
items: [
{
name: 'DASHBOARD',
url: '/dashboard',
icon: 'icon-speedometer',
},
{
name: 'SCHEDULE',
url: '/schedule',
icon: 'icon-calendar',
},
{
name: 'USERS',
url: '/users',
icon: 'icon-user',
},
{
name: 'LEASING_COMPANY',
url: '/company',
icon: 'icon-organization',
},
],
};
and
import { translate } from 'react-i18next';
import nav from '../Sidebar/_nav';
/...
render() {
const test = nav.items.map((item) => {
return <li key={item.url}>{t(item.name)}</li>;
});
return (
<ul>{test}</ul>
);
}