Pass json data from one component to an other. Vue.js - javascript

I'm trying all day to figure out how to pass data from one component to another. I have read a lot of relevant tutorials and instructions, unfortunately with out luck.
I have fetched some data from an api and i present them in the home.vue
and i want to pass the data into a new file to generate a page that will show a random product from the list.
Maybe the approach is totally wrong, but it is the first time that i use vue components, i have experience just with the instance
I'm trying to implement it using props to return the data to the new page.
Here is the randomize.vue file where I would like to pass my data
<template>
<div class="hello">
<p> {{ this.propsdata[0].e }} </p>
<h1>dla;dl;djal;d</h1>
</div>
</template>
<script>
export default {
name: "randomize",
props: ["propsdata"],
data() {
return {
obj: this.propsdata
};
},
mounted(){
console.log(this.props);
},
};
</script>
This is the home.vue file that i fetch the data
<template>
<div>
<div class=" main-conte" >
<randomize :propsData=toBeShown />
<transition-group name="fade" tag="div" id="container" class=" row " >
<figure v-for="(value,index) in toBeShownOrdered" id="page-wrap" :key="index" class="beer-container col-xs-6 col-sm-6 col-lg-4 col-xl-2" >
<a >
<img #click="goTodetail(value.id)" class="logo lazy img-responsive loaded" v-bind:src="getMissingImg(index)"/>
<figcaption>
<div class="beer-title">{{value.name}}</div>
<div class="beer-availability"> {{value.tagline}}</div>
<div class="learn-more">
<h4 class="beer-info-title">Format</h4>
<span class="serving-icons"></span>
<div class="serving">
<i v-if="value.abv >= 0 && value.abv <=6 " class="fas fa-wine-glass-alt"></i>
<i v-if="value.abv >= 6 && value.abv <=7" class="fas fa-glass-cheers"></i>
<i v-if="value.abv >= 7 && value.abv <=100" class="fas fa-wine-bottle"></i>
<span class="measure">{{value.abv}}</span>%</div>
</div>
</figcaption>
</a>
</figure>
</transition-group>
<div class=prev-next>
<button #click="orderByName = !orderByName">Click Me!</button>
<button class="prev" #click="prevPage" :disabled="currentPage==1">
<i class="fas fa-angle-double-left"></i></button>
<button class="next" #click="nextPage" :disabled="currentPage == totalPages">
<i class="fas fa-angle-double-right"></i> </button>
</div>
</div>
<div>
</div>
</div>
</template>
<script>
import { mdbView, mdbMask } from "mdbvue";
import FadeTransition from "./fade-transition.vue";
import randomize from "#/components/randomize";
export default {
name: "home",
components: {
mdbView,
mdbMask,
FadeTransition,
randomize
},
data() {
return {
items: [],
message: '',
currentPage: 1,
orderByName: false,
};
},
computed: {
//show more less products
toBeShown() {
return this.items.slice(0, this.currentPage * 5);
},
totalPages() {
return Math.ceil( this.items.length / 4);
},
toBeShownOrdered() {
return this.orderByName ? _.orderBy(this.toBeShown, 'name', 'asc') : this.toBeShown;
}
},
mounted() {
this.fetchData();
},
methods: {
fetchData: function() {
const myRequest = new Request("https://api.punkapi.com/v2/beers");
fetch(myRequest)
.then(response => {
return response.json();
})
.then(data => {
this.items = data;
console.log(this.items);
})
.catch(error => {
console.log(error);
});
},
getMissingImg(index) {
return this.images[index];
},
nextPage(){
if(this.currentPage < this.totalPages) this.currentPage++;
},
prevPage(){
this.currentPage = this.currentPage - 1 || 1;
},
goTodetail(prodId) {
let proId=prodId
this.$router.push({name:'blog',params:{Pid:proId}})
},
index.js
import Vue from 'vue'
import Router from 'vue-router'
import home from '#/components/home'
import blog from '#/components/blog'
import randomize from '#/components/randomize'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/',
name: 'home',
component: home,
props:true
},
{
path: '/blog/:Pid',
name: 'blog',
component: blog,
props:true
},
{
path: '/randomize/',
name: 'randomize',
component: randomize,
props:true
},
]
})

You would benefit from using vuex as it will keep your state at the application level (as opposed to component data which keeps each component state at the component level).
Setting up vuex requires a bit more work and has a learning curve, but unless you won't grow your app to a medium/large size it will in the long term benefit you by decreasing the overall complexity of your app.
In short, all components from your app can access the state stored in vuex without having to deal with props. And from any component, you can dispatch actions implemented in your vuex store to alter the vuex state. Vuex will help keeping your components focused on presenting data and capturing user actions.
To ease setting up a Vue app with vue-router and vuex, you could choose to build your app with nuxt.js which is a framework that provides you with vue+vue-router+vuex with no effort. Nuxt.js will also help setting up server side rendering which would be beneficial to SEO if your app is to be indexed by search engines.

Related

problem in looping and passing data in vue component

guys its my first time to ask a question here in stackoverflow and i really needs an answer
i have a project which i get data from external api from pinia (similar to VueX) then i pass them into a page then i loop through the data and purse them into a component card to be a dynamic component which renders what ever the data i get
i am having a problem in passing the data into the dynamic component.
i fetched the data successflly in pinia , store it into the state in the store . but cant make it into a variable to loop through them
first iam using typescript
for shop interface ShopData.ts
export default interface ShopData {
id: string
name: string
logoPath: string
address: string
}
for types.ts
export type Shop = ShopData
that is my ShopQueries.ts
import { acceptHMRUpdate, defineStore } from 'pinia'
import type { Shop } from '~/types'
import { getShops } from '~/api/ShopsQueries'
export const useShopQueriesStore = defineStore('ShopQueries', {
state: () => ({
shops: [] as Shop[],
}),
actions: {
async getShops(num: number) {
const response = await getShops(num)
this.shops = response.data
return this.shops
},
},
})
if (import.meta.hot)
import.meta.hot.accept(acceptHMRUpdate(useShopQueriesStore, import.meta.hot))
the page file index.vue
<script setup lang="ts">
import { useShopQueriesStore } from '~/stores/ShopQueries'
import type { Shop } from '~/types'
const shopStore = useShopQueriesStore()
const shops = ref<Shop[] | null>()
onMounted(async() => {
shops.value = await shopStore.getShops(6)
})
</script>
<template>
<div class="row">
<div class="col-md-6 col-xxl-4 mt-3 my-3">
<ShopCard
v-for="shop in shopStore.$state.shops"
:key="shop.id"
:address="shop.address"
:name="shop.name"
:image="shop.logoPath"
/>
</div>
</div>
</template>
Which i also want to make it a card and wraps down and i cant :(
that is the card component ShopCard.vue
<script setup lang="ts">
import type { PropType } from '#vue/runtime-core'
import type { Shop } from '~/types'
const props = defineProps({
shop: null as null | PropType<Shop>,
})
console.log(props)
onMounted(() => {
})
const { shop } = toRefs(props)
</script>
<template>
<div class="card">
<div class="card-body d-flex flex-center flex-column pt-12 p-9">
<div class="symbol symbol-65px symbol-circle mb-5">
<img src="{{shop.image}}" alt="image">
</div>
<a class="fs-4 text-gray-800 text-hover-primary fw-bolder mb-0" href="">{{ shop.name }}</a>
</div>
<div class="fw-bold text-gray-400 mb-6">
{{ shop.address }}
</div>
</div>
</template>
i know its hard .. but i really needs some help please !
the whole task depends on it
waiting for help ...

How to change pagination variables in react

I am working on building pagination. I'm still working on my API to fetch posts based on pagination, but at the moment I am stuck on a problem with states.
In my main file (where the posts will be), my code looks like this:
// Import modules
import React from "react";
import axios from "axios";
import url from "url";
// Import components
import { Snippet } from "../Snippet";
import { CreatePost } from "./CreatePost";
import { Pagination } from "./Pagination";
// Import styles
import "./css/Content.css";
// Axios Init
const api = axios.create({
baseURL: `http://localhost:5000/api/`,
});
export class Content extends React.Component {
state = {
collections: [
{ title: "ReactJS", color: "red" },
{ title: "HTML", color: "cyan" },
{ title: "CSS", color: "pink" },
],
snippets: [],
limitSnippets: 3,
page: 0,
};
constructor(props) {
super(props);
}
componentDidMount() {
this.getSnippets();
}
getSnippets = async () => {
try {
let data = await api
.get(
`/snippets/fetchAll?limitSnippets=${this.state.limitSnippets}&page=${this.state.page}`,
{
body: {
limitSnippets: 3,
page: 1,
},
}
)
.then(({ data }) => data);
this.setState({ snippets: data });
} catch (error) {
console.log(error);
}
};
updatePagination = (page) => {
this.state.page = page;
console.log(this.state.page);
};
render() {
return (
<div className="content">
<h1 className="content-header">Snippets</h1>
<CreatePost contentUpdater={this.getSnippets} />
<Pagination updatePagination={this.updatePagination} />
<div className="w-layout-grid grid">
{this.state.snippets.map((snippet, i) => {
return (
<Snippet
key={i}
title={snippet.title}
code={snippet.code}
updatedDate={snippet.updatedDate}
createdDate={snippet.createdDate}
language={snippet.language}
creator={snippet.creator}
collections={snippet.collections}
/>
);
})}
</div>
<Pagination />
</div>
);
}
}
export default Content;
In pagination file, my code looks like this:
export const Pagination = (props) => {
// States
const [page, setPage] = useState(0);
// Axios Init
const api = axios.create({
baseURL: `http://localhost:5000/api/`,
});
const handleLeft = (event) => {
event.preventDefault();
if (page > 0) {
setPage(page - 1);
props.updatePagination(page);
} else {
console.log("handleLeft(): page not > 0");
}
//props.updatePagination(page);
//}
};
const handleRight = (event) => {
event.preventDefault();
// page < fetchAllPages
setPage(page + 1);
props.updatePagination(page);
};
/*useEffect(() => {
props.updatePagination(page);
}, [page]);
*/
return (
<div className="paginate-div">
<div className="paginate-next">
<div className="paginate-next-icon" onClick={handleLeft}>
<i className="fas fa-caret-left"></i>
</div>
</div>
<a href="#" className="paginate-button first w-inline-block">
<div className="paginate-text">1</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">2</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">3</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">4</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">5</div>
</a>
<a href="#" className="paginate-button w-inline-block">
<div className="paginate-text">6</div>
</a>
<a href="#" className="paginate-button last w-inline-block">
<div className="paginate-text">...</div>
</a>
<div className="paginate-next" onClick={handleRight}>
<div className="paginate-next-icon">
<i className="fas fa-caret-right"></i>
</div>
</div>
</div>
);
};
I have my pagination component which is passed a prop that's a function to updatePagination(). The pagination component has functions for left and right button for switching thru pagination, and when it is clicked, the main file gets the pagination updated.
The problem I am having (sorry if it is confusing by the way i worded this)
The default page is 0 (which is basically page 1).
The crazy thing is when I press right (handleRight is called on submit), it stays at page 0, then if I click it again it goes to 1, then after if I press the left button (which called handleLeft on submit) while it is on page 1, it goes up to 2 somehow, but if I click it again it goes back down to 1, then if I click again it goes to 0.
Why is this strange problem occurring?
setPage is asynchronous, so when you setPage to decrement and then immediately call props.updatePage, props.updatePage is receiving the old value of page. You can read all about this common problem here.
const handleRight = (event) => {
event.preventDefault();
// Hey React, set page to page + 1 and rerender the component
setPage(page + 1);
// But before you do that, call props.updatePagination on the old value
props.updatePagination(page);
};
You should ask yourself, though, why you even store two stateful values of page at all (one in the parent component, one in the child component). Couldn't the Content component keep track of your page state (as it already does) and pass it down as a prop, getting rid of your need for a page state in the child component? This is called Lifting State Up, and it's a fundamental concept in React, which incentivizes you to use the least amount of state possible to avoid exactly this kind of desync. Furthermore, from the code you've shared, the Pagination component just displays the page numbers - why does it even need to be stateful at all?
The problem was that the old value of page was being used in updatePagination(), I fixed this by not running updatePagination(page) in the same place, I used useEffect(), and checked for changes to {page}, and ran updatePagination in there.
useEffect(() => {
props.updatePagination(page);
}, [page]);
The handleLeft, handleRight functions were changed to look like this:
const handleLeft = (event) => {
event.preventDefault();
let newPage = page - 1;
if (page > 0) {
setPage(newPage);
} else {
console.log("handleLeft(): page not > 0");
}
//}
};
NOTE"
In the comments section, someone made a point that I should not be storing the page number in two places, but rather store them in one place and pass it over as props. I have not currently tried to do this, but I plan on doing this soon. But for now, this is the best answer for this scenario.

Vue.js - How to dynamically bind v-model to route parameters based on state

I'm building an application to power the backend of a website for a restaurant chain. Users will need to edit page content and images. The site is fairly complex and there are lots of nested pages and sections within those pages. Rather than hardcode templates to edit each page and section, I'm trying to make a standard template that can edit all pages based on data from the route.
I'm getting stuck on the v-model for my text input.
Here's my router code:
{
path: '/dashboard/:id/sections/:section',
name: 'section',
component: () => import('../views/Dashboard/Restaurants/Restaurant/Sections/Section.vue'),
meta: {
requiresAuth: true
},
},
Then, in my Section.vue, here is my input with the v-model. In this case, I'm trying to edit the Welcome section of a restaurant. If I was building just a page to edit the Welcome text, it would work no problem.:
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
This issue is that I need to reference the "welcome" part of the v-model dynamically, because I've got about 40 Sections to deal with.
I can reference the Section to edit with this.$route.params.section. It would be great if I could use v-model="restInfo. + section", but that doesn't work.
Is there a way to update v-model based on the route parameters?
Thanks!
Update...
Here is my entire Section.vue
<template>
<div>
<Breadcrumbs :items="crumbs" />
<div v-if="restInfo">
<h3>Update {{section}}</h3>
<div class="flex flex-wrap">
<div class="form__content">
<form #submit.prevent>
<vue-editor v-model="restInfo.welcome" placeholder="Update Text"></vue-editor>
<div class="flex">
<button class="btn btn__primary mb-3" #click="editText()">
Update
<transition name="fade">
<span class="ml-2" v-if="performingRequest">
<i class="fa fa-spinner fa-spin"></i>
</span>
</transition>
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</template>
<script>
import { mapState } from 'vuex'
import { VueEditor } from "vue2-editor"
import Loader from '#/components/Loader.vue'
import Breadcrumbs from '#/components/Breadcrumbs.vue'
export default {
data() {
return {
performingRequest: false,
}
},
created () {
this.$store.dispatch("getRestFromId", this.$route.params.id);
},
computed: {
...mapState(['currentUser', 'restInfo']),
section() {
return this.$route.params.section
},
identifier() {
return this.restInfo.id
},
model() {
return this.restInfo.id + `.` + this.section
},
crumbs () {
if (this.restInfo) {
let rest = this.restInfo
let crumbsArray = []
let step1 = { title: "Dashboard", to: { name: "dashboard"}}
let step2 = { title: rest.name, to: { name: "resthome"}}
let step3 = { title: 'Page Sections', to: { name: 'restsections'}}
let step4 = { title: this.$route.params.section, to: false}
crumbsArray.push(step1)
crumbsArray.push(step2)
crumbsArray.push(step3)
crumbsArray.push(step4)
return crumbsArray
} else {
return []
}
},
},
methods: {
editText() {
this.performingRequest = true
this.$store.dispatch("updateRest", {
id: this.rest.id,
content: this.rest
});
setTimeout(() => {
this.performingRequest = false
}, 2000)
}
},
components: {
Loader,
VueEditor,
Breadcrumbs
},
beforeDestroy(){
this.performingRequest = false
delete this.performingRequest
}
}
</script>
Try to use the brackets accessor [] instead of . :
<vue-editor v-model="restInfo[section]"

Can i dynamically call a getter using mapGetters in Vue/Vuex?

My component template:
<template>
<section class="stage my-5">
<div class="stage-heading">
<h3 class="stage-number mb-4">Stage {{stage}}</h3>
<h6 class="stage-hrs">Total Hrs: {{totalHours}}</h6>
</div>
<div class="stage-courses card text-white bg-info mb-3" v-for="course in courses" :key="course.id">
<div class="card-header">Stage {{course.stage}}</div>
<div class="card-body">
<h5 class="card-title">{{course.title}}</h5>
<p class="card-text">{{course.creator}}</p>
<p class="card-text">{{course.hours}} Hours</p>
</div>
</div>
</section>
</template>
The state in my Vuex store:
const state = {
roadmapStage1: [],
roadmapStage2: [],
roadmapStage3: [],
};
I have getters in my Vuex store that look like:
getRoadmapStage1: state => state.roadmapStage1,
getRoadmapStage2: state => state.roadmapStage2,
getRoadmapStage3: state => state.roadmapStage3,
I'm trying to dynamically call one of these getters from a component, which one depends on a prop of the component:
export default {
name: "Stage",
data() {
return {
courses: []
}
},
props: ['stage'],
computed: mapGetters({courses: 'getRoadmapByStage'})
}
Is there any way to insert the prop into the 'getRoadmapByStage'? e.g. so it functions like
getRoadmapByStage${stage}?
Ultimately i'm trying to get the component to re-render anytime one the roadmapStage arrays are updated. Thanks!
I would suggest using a getter with a parameter for the stage id/number that returns the roadmap you want, like so:
// in getters.js
//gets the current roadmap based on the stage number
getRoadmapByStage: (state) => (stageNumber) => {
return state["roadmapStage" + stageNumber];
}
now in your component you can have:
computed: {
currentRoadmap() {
// now we'll pass in your 'stage' prop to get the appropriate map
// this will re-render the component as that prop changes
return this.$store.getters.getRoadmapByStage(this.stage);
}
}
You can declare your computed roadmap property as follows:
computed: {
roadmap() {
return this.stage ? this.$store.getters['getRoadmapByStage' + this.stage] : undefined
},
}
That way you are getting the roadmap by the value of the prop or undefined if the prop is not set to anything.

Problems with data communication between components

I had a page on which there was a header with an input that was a search engine, a list of posts, and pagination. I decided to move the header from this file to a separate component in a separate vue file. After I did this, the search for posts by title stopped working, and I can’t add a post now either. I think that I need to import my posts into a new file for my newly created component but how to do it.
My code when it worked(before my changes)
My code is not working after the changes:
The file in which my posts situated:
<template>
<div class="app">
<ul>
<li v-for="(post, index) in paginatedData" class="post" :key="index">
<router-link :to="{ name: 'detail', params: {id: post.id, title: post.title, body: post.body} }">
<img src="src/assets/nature.jpg">
<p class="boldText"> {{ post.title }}</p>
</router-link>
<p> {{ post.body }}</p>
</li>
</ul>
<div class="allpagination">
<button type="button" #click="page -=1" v-if="page > 0" class="prev"><<</button>
<div class="pagin">
<button class="item"
v-for="n in evenPosts"
:key="n.id"
v-bind:class="{'selected': current === n.id}"
#click="page=n-1">{{ n }} </button>
</div>
<button type="button" #click="page +=1" class="next" v-if="page < evenPosts-1">>></button>
</div>
</div>
</template>
<script>
import axios from 'axios';
export default {
name: 'Pagination',
data () {
return {
search: '',
current: null,
page: 0,
posts: [],
createTitle: '',
createBody: '',
visiblePostID: '',
}
},
watch: {
counter: function(newValue, oldValue) {
this.getData()
}
},
created(){
this.getData()
},
computed: {
evenPosts: function(posts){
return Math.ceil(this.posts.length/6);
},
paginatedData() {
const start = this.page * 6;
const end = start + 6;
return this.posts.filter((post) => {
return post.title.match(this.search);
}).slice(start, end);
},
},
methods: {
getData() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(response => {
this.posts = response.data
})
},
}
}
</script>
Header vue:
AddPost
<script>
import axios from 'axios';
export default {
name: 'Pagination',
data () {
return {
search: '',
current: null,
posts: [],
createTitle: '',
createBody: '',
}
},
created(){
this.getData()
},
methods: {
getData() {
axios.get(`https://jsonplaceholder.typicode.com/posts`).then(response => {
this.posts = response.data
})
},
addPost() {
axios.post('http://jsonplaceholder.typicode.com/posts/', {
title: this.createTitle,
body: this.createBody
}).then((response) => {
this.posts.unshift(response.data)
})
},
}
}
</script>
App.vue:
<template>
<div id="app">
<header-self></header-self>
<router-view></router-view>
</div>
</template>
<script>
export default {
components: {
name: 'app',
}
}
</script>
You have a computed property paginatedData in your "posts" component that relies a variable this.search:
paginatedData () {
const start = this.page * 6;
const end = start + 6;
return this.posts.filter((post) => {
return post.title.match(this.search);
}).slice(start, end);
},
but this.search value is not updated in that component because you moved the search input that populates that value into the header component.
What you need to do now is make sure that the updated search value is passed into your "posts" component so that the paginatedData computed property detects the change and computes the new paginatedData value.
You're now encountering the need to pass values between components that may not have a parent/child relationship.
In your scenario, I would look at handling this need with some Simple State Management as described in the Vue docs.
Depending on the scale of you app it may be worth implementing Vuex for state management.

Categories

Resources