How can I pass data from parent to nested components? - javascript

I'm creating a Vue3 web app and I was wondering what is the best way to pass data from parent to nested components (more specific, from parent to child of children).
Let's say I have this schema:
Card
|
|
CardHeader CardBody
|
|
BodyHeader BodyParams BodyResponse
And I want to know the best way to pass data from Card to BodyHeader, BodyParams and BodyResponse
On Card Component I will be fetching some data from an API. Lets say that i fetch a json like this:
{
header: {
title: 'hello header'
},
body: {
headers: ['authorization', 'anotherheader']
params: ['currency', 'hair'],
response: ['200', '401']
}
}
I know I just could do this:
<Card/> :
<template>
<CardHeader :header="hedaer"></CardHeader>
<CardBody :body="body"></CardBody>
</template>
Then, in <CardBody/> :
<template>
<CardBodyHeader :headers="body.headers"></CardBodyHeader>
<CardBodyParams :params="body.params"></CardBodyParams>
<CardBodyResponse :responses="body.responses"></CardBodyResponse>
</template>
<script>
import CardBodyHeader from "./CardBodyHeader.vue";
import CardBodyParams from "./CardBodyParams.vue";
import CardBodyResponse from "./CardBodyResponse.vue";
export default {
components: { CardBodyHeader, CardBodyParams, CardBodyResponse },
props: {
body: {type: Object}
}
};
</script>
But I don't know if that would be the optimal solution.

I guess there are many ways, you could do that.
Easiest one would be state management, and you can use something like Pinia and Vuex to do so or maybe even write your own store (But why do that when you have Pinia).
Second one would be the usage of props, but you should keep in mind that props should never be mutated.
==> https://vuejs.org/guide/components/props.html
Third one would be the use of Vue3 (Provide / Inject) as seen on the docs ==> https://vuejs.org/guide/components/provide-inject.html
You can also send data from a child to a parent component Vue's built-in $emit() method.
But of course each and every one of these is solving different problems, so try to have a quick read on docs about each and every one of them to solve the issue you're facing, and to know which to use on the upcoming issues you might face.

If you don't use any state management library like Vuex or pinia.
I suggest you to use provide/inject to prevent props drilling.
You need to provide the data from the most parent component, and in the child components that need these data, just use inject
Ex parent:
export default {
provide: {
message: 'hello!'
}
}
Ex: nested child
export default {
inject: ['message'],
created() {
console.log(this.message) // injected value
}
}

Related

How to share variables between functions in Vue

I'm learning Vue and trying to test out how I'd pass a user input from one function to another.
My use case is this. I want to take a users input, store it and then supply that to a second function as a variable that it can use to make an API call.
In isolation the two code sections are:
User Input
<div id="v-model-basic" class="demo">
<input v-model="message" placeholder="edit me" />
<p>Message is: {{ message }}</p>
</div>
Vue.createApp({
data() {
return {
message: ''
}
}
}).mount('#v-model-basic')
API Call
import axios from 'axios';
/* API call for the weather data */
export default {
name: "Weather",
data() {
return {
weatherList: []
};
},
methods: {
getweatherData() {
axios.get("https://anAPIpath.com/that-I-want-to-add-user-input-to").then(response => (this.weatherList = response.data));
}
}
};
Is there a way to do this in Vue where they are both methods within that export function, or is that just the wrong approach? As mentioned, new and learning Vue and I want to ensure I approach this the "Vue way" so things are as neat and tidy as possible.
Vue has several options for passing variables between components.
props
You can make the variable a prop of the other:
props: {
message: String
}
The prop is accessed with this.message but it is recommended you copy it to a variable in data and reflect changes with $emit (see documentation link, and $emit section below).
See documentation: https://v3.vuejs.org/guide/component-props.html#props
$emit
You can let a parent component know about the other component's changes with $emit. You can just name an event yourself and pass the value as the first parameter.
this.$emit('messageUpdate', this.message);
See documentation: https://v3-migration.vuejs.org/breaking-changes/emits-option.html#emits-option
VueX
With the VueX data storage you can transfer variables from any component across your app to any other.
This takes a little more finess to set up. I suggest going with the above first.
If you are interested (definitely worth to learn), here is the link:
https://vuex.vuejs.org/guide/#getting-started
I see you are doing well, you just need to add the API call to a script tag in the same file and then access the input message like this:
axios.get("https://anAPIpath.com/"+ this.message)

Use <component> that was imported in the parent

I'm building a component that manages other components.
It dynamically render components in specific places depending on the props and inputs, much like an orchestrator.
Use case
My orchestrator have the following placeholders, like a grid (p1 ... p6):
|-p1-|-p2-|-p3-|
|-p4-|-p5-|-p6-|
In a given moment, it renders the component C1 into p2 and C2 into p6:
|-p1-|-C1-|-p3-|
|-p4-|-p5-|-C2-|
In another moment, it replaces the C1 by C3:
|-p1-|-C3-|-p3-|
|-p4-|-p5-|-C2-|
The problem
Given this dynamism, I can't (as far as I know) use slots. So I'm using component tags with the :is prop in order to dynamically render the correct component. The problem is that in order for the :is to work, I must have the component defined in my orchestrator/manager component. And I would like to keep it separated for reuse, doesn't make sense to import the components there. One solution would be to globally register the components. I would like to avoid that if possible. Is there a way? I'm open to any kind of reflection-magic you may think n_n'
You can just pass the component via a prop like this:
Orchestrator.vue
<component :is="comp"/>
export default {
props: ['comp']
}
Test.vue
<orchestrator :comp="MyComponent"/>
import Orchestrator from './orchestrator.vue'
import MyComponent from './my-component.vue'
export default {
components: {
Orchestrator,
},
data() {
return {
MyComponent,
};
},
}
You should be able to use async components, loaded dynamically.
For example...
<component :is="p2Replacement"></component>
data () {
return {
p2Replacement: null
}
},
methods: {
setP2Replacement(component) {
this.p2Replacement = () => import(`path/to/${component}.vue`)
}
}
And call it like setP2Replacement('C1') or setP2Replacement('C3').
The fixed parts in the import expression are required so Webpack can include the appropriate files using a glob pattern. See https://webpack.js.org/guides/dependency-management/#require-context

Changing object from external file

I am learning Vue and before that I have some experience working with React.
As, I move on comprehending the basics of vue, I sort of compare things with JS
<template>
<div id="app">
<HelloWorld msg="Below This we will inclue our To-do task list"/>
<p>{{msg}}</p>
<Todos :toDoItem="todos"/>
</div>
</template>
import Todos from "./components/todo.vue";
let todo =
{
name: "Rohit",
title: "Full Stack Developer",
completed: true
}
export default {
name: "app",
components: {
HelloWorld,
Todos
},
data() {
return {
msg: "hello",
todos: todo
};
}
};
We are passing the props here to child component and changing it using method
<template>
<div>
<div class="main-class">
<p>{{toDoItem.title}}</p>
<input type="checkbox" v-on:change="markComplete">
</div>
</div>
</template>
<script>
export default {
name: "toDoItem",
props: {
toDoItem: {
type: Object
}
},
method: {
markComplete() {
this.toDoItem.complete = !this.toDoItem.complete
}
};
</script>
This is where I am unable to comprehend many things.
Question 1: Based on my understanding of this, shouldn't it point to a global space and hence this should be undefined?
markComplete() {
this.toDoItem.complete = !this.toDoItem.complete
}
Question 2: In react we can't change props passed to a child object probably? but here we are changing the props? Also, In general, if we declare a object in another file (app.js), can we change it to in another file (someApp.js)?
All methods in a Vue instance are bound to the instance so that this works properly.
You should not be changing props. Here, you are changing a member of a prop -- toDoItem is a prop, and complete is a member of it -- which is not controlled, but you still should not do it. You should instead emit an event that the parent handles.
Your markComplete should be in the parent, since it owns the data being manipulated. It would look like:
markComplete() {
this.todos.complete = !this.todos.complete;
}
In the child, the method would be something like
toggleComplete() {
this.$emit('toggle');
}
And the parent would use v-on to handle the toggle event:
<Todos :toDoItem="todos" v-on:toggle="markComplete"/>
This way all the code that manipulates data owned by a component happens within that component. Nobody calls anybody else's methods or modifies their data.
I think you intend for todos to be an array of items, and probably want to v-for over them, having one child component for each item, but that's another issue.
I cannot answer your last question about objects declared in different files, because I don't understand it. It is not unusual to import objects and modify them, generally. As I mentioned, though, Vue components should be the only ones to modify their own data.

MobX store doesn't get updated in react

What i want to achieve
I have a store object and i want to populate one of it's properties (which is an empty array) with instances of another object. And i want one of my react component automatically updated when part of the mentioned array instance is changed.
What my problem is
By logging out the value of this constructed store object i can see the change in the browser console, but it doesn't get updated automatically in the react component when its value changed.
So i'd like to get hints, examples of how to implement a solution like this in general.
Details
My project
I want to create a MobX store called Session which would store everything my react webapp needs.
This webapp would be a document handling tool, so after creating new or loading existing documents i want to add the Document object to the Session (into an object array called documents).
Some more details: a Document consists of one or more section. So using a WYSIWYG editor i add its content to the given section every time it's content changes.
Problem
I can add a new Document to the Session, i can update section as well(I can log out the value of a section in console), but using the Session reference to this document and section in a react component it doesnt update its state when section value is changed.
To my understanding in my example the reference of a Document is not changed when the value of a section is changed and hence it doesn't trigger MobX to react.
What i found so far
I started to dig in the deep, dark web and found this article.
So i started getting excited since asStructure (or asMap) seemed to solve this issue, but it looks like asStructure in MobX is deprecated.
Then i found this issue thread, where a thing called observable.structurallyCompare is mentioned. But again i found nothing about this in MobX documentation so im puzzled how to implement it.
So im stuck again and have no idea how to solve this problem.
Code excerpts from my project
This is how i reference to the mentioned Session value in the main react component:
import userSession from '../client/Session';
import {observer} from 'mobx-react';
#observer class App extends React.Component {
...
render() {
return (
...
<div>{JSON.stringify(userSession.documents[0].content.sections)}</div>
...
This is how i update the section in the editor react component:
import userSession from '../../client/Session';
...
handleChange(value,arg2,arg3,arg4) {
this.setState({ content: value, delta: arg4.getHTML()});
userSession.documents[0].updateSectionContent(this.props.id, this.state.delta);
}
}
...
Session.js excerpt:
class Session {
constructor(){
extendObservable(this, {
user: {
},
layout: {
},
documents: []
})
//current hack to add an empty Document to Session
this.documents.push(new Document());
}
//yadayadayada...
#action addNewSection() {
userSession.documents[0].content.sections.push({
type: "editor",
id: userSession.documents[0].getNextSectionID(),
editable: true,
content: "",
placeholder: "type here to add new section..."
});
}
}
var userSession = window.userSession = new Session();
export default userSession;
Document.js
import {extendObservable, computed, action} from "mobx";
import userSession from "./Session";
class Document {
constructor(doc = null) {
if (doc == null) {
console.log("doc - no init value provided");
extendObservable(this,{
labels: {
title: "title",
description: "description",
owners: ["guest"]
},
content: {
sections: [
{
type: "editor",
id: "sec1",
editable: true,
placeholder: "this is where the magic happens"
},
]
}
})
} else {
console.log("doc - init values provided: ");
extendObservable(this,{
labels: doc.labels,
content: doc.content
});
}
}
getNextSectionID(){
return `sec${this.content.sections.length}`;
}
#action updateSectionContent(sectionID, delta) {
console.log("doc - looking for section " + sectionID + " to update with this: " + delta);
let index = this.content.sections.findIndex(
section => section.id === sectionID
);
userSession.documents[0].content.sections[index].content = delta;
}
}
export default Document;
Ps.: atm moment i don't remember why i made Document properties observable, but for some reason it was necessary.
Unfortunately, you are implementing mobx with React the incorrect way. To make it more comprehensible, I suggest you look into the implementation of the HOC observer that mobx-react provide.
Basically, what this HOC does is to wrap your component inside another React component that implement shouldComponentUpdate that check when the props referred inside render function change, and trigger the re-render. To make React component reactive to change in mobx store, you need to pass the store data to them as props, either in React traditional way, or via the inject HOC that mobx-react provide.
The problem while your code does not react to change in the store is because you import the store and use them directly inside your component, mobx-react cannot detect change that way.
To make it reactive, instead of import it and use it directly in your App component, you can pass the store as a prop as follow:
#observer
class App extends React.Component {
...
render() {
return (
...
<div>{this.props.sections}</div>
...);
}
}
Then pass the store to App when it's used:
<App sections={userSession.documents[0].content.sections} />
You can also have a look at how to use inject here: https://github.com/mobxjs/mobx-react.
An just a suggestion: before jumping directly on some best pattern, try to stick with the basic, standard way that library author recommend and understand how it works first, you can consider other option after you got the core idea.

Rendering a list based on a shared property in Vue

I'd like to render out a list of components based on whether the type property is of a certain type. For example, if resource.type === 'article' then render all of the resources with type article, etc.
My resources array looks like this
I have created a component that is basically just a view.
<template>
<div class="type-feed">
<resource-card></resource-card>
</div>
</template>
<script>
import Vue from 'vue';
import VueFire from 'vuefire';
import ResourceCard from '../components/ResourceCard'
var resourcesRef = firebase.database().ref('resources');
// explicit installation required in module environments
Vue.use(VueFire);
export default {
name: 'type-view',
components: {
ResourceCard
},
data () {
return {
}
},
firebase: {
resources: resourcesRef
}
}
</script>
<style>
</style>
The <resource-card> component should take in the list and render a specific information on an individual resource object. Something like the following
<resource-card v-for="(resource, index) in resources" :resource="resource>
My question is, what is the best way to render a list by resources.type?
If <resource-card> is not used anywhere else, then just pass the resource to it and let it decide what to render according to resource.type should be enough.
If you could need a article card later elsewhere, or you want a more elegant design, then you may define a component for each resource type, and in the v-for, you can use dynamic components to render different components in one loop.

Categories

Resources