Naourass Derouichi
Naourass Derouichi

Reputation: 793

Vue JS Pass Data From Parent To Child Of Child Of Child

In Vue.js, how to correctly pass data from parent component to a chain of multi level child components?

Upvotes: 0

Views: 2739

Answers (2)

RoboKozo
RoboKozo

Reputation: 5062

You have a few options:

  1. Props
  2. Event Bus
  3. Vuex
  4. Provide/Inject

Find out more here: https://www.smashingmagazine.com/2020/01/data-components-vue-js/

Upvotes: 5

FullStackOfPancakes
FullStackOfPancakes

Reputation: 1381

@RoboKozo listed some really great options for you, it just depends on what you're doing with the data.

If it's static data (and you like to be modular), you can always make use of mixins.

From the Vue.js docs:

Mixins are a flexible way to distribute reusable functionalities for Vue components. A mixin object can contain any component options. When a component uses a mixin, all options in the mixin will be “mixed” into the component’s own options.

Example 1: Mixins

File/Folder Setup:

/src/mixins/defaultDataMixin.js // Call this whatever you want

/src/mixins/defaultDataMixin.js:

export const default defaultData {
    data() {
        return {
            property1: "Foo",
            property2: "Bar"
        }
    }
}; 

Now, on any component you want to have access to property1 or property2, you can just import your mixin.

/src/pages/MyComponent.vue

<template>
    <div id="wrapper">
        <div class="container">
            <div class="row">
                <div class="col">
                    {{ property1 }}
                </div>
                <div class="col">
                    {{ property2 }}
                </div>
            </div>
        </div>
    </div>
</template>
<script>
import { defaultData } from "@/mixins/defaultDataMixin"

export default {
    mixins: [defaultData],
}
</script>

Example 2: Sharing data via Props & Emit

Props down, Emit up

Perhaps you return some data in a Parent component that you wish to pass down to be used in a child component. Props are a great way to do that!

File/Folder Setup

/src/pages/ParentComponent.vue
/src/components/ChildComponent.vue

ParentComponent.vue

<template>
    <div id="wrapper">
        <div class="container">
            <div class="row">
                <div class="col">
                    <child-component :defaultDataProp="defaultData" @emitted-text="helloWorld">
                    </child-component>
                </div>
            </div>
        </div>
    </div>
</template>
<script>
import { defaultData } from "@/mixins/defaultDataMixin"
import ChildComponent from "@/components/ChildComponent.vue"

export default {
    mixins: [defaultData],
    components: {
        ChildComponent
    },
    data() {
        return {
            emittedText: "",
        }
    },
    methods: {
        helloWorld(e){
            this.emittedText = e;
        }
    }
}
</script>

ChildComponent.vue

<template>
    <div id="wrapper">
        <div class="container">
            <div class="row">
                <div class="col">
                    {{ defaultDataProp }}
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <button @click="emitData">Emit Data</button>
                </col>
            </div>
        </div>
    </div>
</template>
<script>
export default {
    props: {
        defaultDataProp: {
            type: String
        }
    },
    data() {
        return {
            text: "Hello, world!"
        };
    },
    methods: {
        emitData() {
            this.$emit('emitted-text', this.text);
        }
    }
}
</script>

Cool, right?

These are just a couple of ways you can share data and functions between components.

** Edit ** Example 3: Vuex state + mapGetters

I just noticed you tagged this state as well, so I'm assuming you have already installed and are using Vuex.

While it's not "passing" the data from parent to child directly, you could have the Parent component can mutate a state that a child component is watching.

Let's imagine you want to return the user information and then have access to that data across multiple components.

File/Folder setup:

/.env 
/src/main.js
/src/store/store.js
/src/store/modules/user.js
/src/mixins/defaultDataMixin.js
/src/util/api.js
/src/pages/ParentComponent.vue
/src/components/ChildComponent.vue

main.js

import Vue from "vue";
import Vuex from "vuex";
import { store } from "@/store/store";
// ... add whatever else you need to import and use

Vue.use(vuex); // but make sure Vue uses vuex

// Vue can "inject" the store into all child components
new Vue({
    el: "#app",
    store: store, // <-- provide the store to the vue instance
    // ..
});

.env:

VUE_APP_API_URL='http://127.0.0.1:8000'

api.js:

import axios from "axios";
import { store } from "@/store/store";

const apiClient = axios.create({
  baseURL: process.env.VUE_APP_API_URL,
});

export default apiClient;

user.js:

import apiClient from "@/util/api"
import { store } from "../store"

const userModule = {
    state: () => ({
        user: {
            firstName: "",
        }
    }),
    mutations: {
        setUser(state, payload) {
            state.user = payload;
        }
    },
    actions: {
        async loadUser({ commit, dispatch }) {
            try {
                const user = (await apiClient.get("/user")).data;
                commit("setUser", user); // mutate the vuex state
            } catch (error) {
                dispatch("logout");
            }
        },
        logout({ commit }) {
            commit("setUser", {});
        }
    },
    getters: {
        firstName: (state) => {
            return state.user.firstName;
        }
    }
   
};

export default userModule;

store.js

import Vue from "vue";
import Vuex from "vuex";
import userModule from "@/store/modules/user"; // Grab our newly created module

Vue.use(Vuex);

export const store = new Vuex.Store({
  modules: {
    userState: userModule,
  },
});

defaultDataMixin.js

import { mapGetters } from "vuex";

export const default defaultData {
    data() {
        return {
            property1: "Foo",
            property2: "Bar"
        }
    },
    // We can make use of mapGetters here 
    computed: {
        ...mapGetters(["firstName"]),
    }
}; 

Now, both Parent and Child have access to the centralized state and can mutate it. For example, let's call the loadUser() from the parent and watch for the state change on the child component.

ParentComponent.vue

<template>
    <div id="wrapper">
        <div class="container">
            <div class="row">
                <div class="col" v-if="firstName != ''">
                    {{ firstName }}
                </div>
                <div class="col">
                    <button @click="mutateState">Click Me</button>
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <child-component @emitted-text="helloWorld">
                    </child-component>
                </div>
            </div>
        </div>
     </div>
</template>
<script>
import { defaultData } from "@/mixins/defaultDataMixin"
import ChildComponent from "@/components/ChildComponent.vue"
export default {
    mixins: [defaultData],
    components: {
        ChildComponent
    },
    methods: {
        // our new method to mutate the user state 
        mutateState() {
            this.$store.dispatch("loadUser");
        },
        helloWorld(e){
            this.emittedText = e;
        }
    }
}
</script>

ChildComponent.vue

<template>
    <div id="wrapper">
        <div class="container">
            <div class="row">
                <div class="col">
                    {{ defaultData }}
                </div>
            </div>
            <div class="row">
                <div class="col">
                    <button @click="emitData">Emit Data</button>
                </col>
            </div>
        </div>
    </div>
</template>
<script>
import { defaultData } from "@/mixins/defaultDataMixin"

export default {
    mixins: [defaultData],
   
    data() {
        return {
            text: "Hello, world!"
        };
    },
    methods: {
        emitData() {
            this.$emit('emitted-text', this.text);
        }
    },
    // Let's watch for the user state to change 
    watch: {
        firstName(newValue, oldValue) {
            console.log('[state] firstName state changed to: ', newValue);
            // Do whatever you want with that information 
        }
    }
}
</script>

Upvotes: 2

Related Questions