Vue: Provided props are not injected into functional components

Created on 7 Jun 2017  ·  16Comments  ·  Source: vuejs/vue

Version

2.3.3

Reproduction link

http://jsfiddle.net/p861bj9y/

Steps to reproduce

I created a minimal reproduction of the behavior I am trying to test, the example just needs JSX to work.

What is expected?

The properties passed down from parent should show up in ctx.injections.

What is actually happening?

Ctx.injections exists but remains empty. The properties are not being passed down to the functional component context.

bug

Most helpful comment

Are there plans to address this issue in v3?

Eg I am trying to abstract a v-for away into a render function but my childs can be functional components (so they are already rendered when entering the render function and I cant clone them).

All 16 comments

It's because that instead of parent, child is considered to be children of vm (maybe an issue). So you may need to write provide in vm.

BTW, your fiddle is using [email protected] 😅

The lookup algorithm for provide inject is the child looks at itself for provided attributes and then loops up it's $parent hierarchy in search of provided props until it is at the root.
https://github.com/vuejs/vue/blob/b182ac40697edbe8253d4bd68b6ac09e93259e1c/src/core/instance/inject.js#L59-L59

Couldn't get your fiddle to run, but when i ran https://jsfiddle.net/Austio/vhgztp59/7/ this fiddle the $parent was undefined on the child component when i got to the lookup context. At least that is a start if this is not an issue with rendering into slots and there not being a relationship between the components.

it looks like the functional component is rendered before slots is resolved

@Kingwl correct, and that's kind of a technical requirement.

I remember I raised the point because I was getting crazy about it. At the end, it looked normal to me because functional components are attached to component they're rendered in and therefore when used in a slot, they get attached to the outer component. However, this is not the case with non-functional components:

Container injects mode: 'foo' and renders <div><slot/></div>

<!-- rendered in App -->
<container>
   <!-- parent is App, mode is undefined -->
  <functional></functional>
</container>

<container>
   <!-- parent is container, mode is foo -->
  <not-functional></not-functional>
</container>

http://jsfiddle.net/p861bj9y/

edit: @alidcastano I updated the fiddle in your comment since yours wasn't even using Vue 2

Haha sorry for using the wrong Vue version in the fiddle, I was too caught up on not being able to configure JSX that I didn't realize. @posva Thanks for fixing my example!

--

So the issue here is not that the functional component can't receive the provided properties, it's that the functional component is rendered before the slot?

@LinusBorg By "technical requirement" does that mean that there is no workaround or that the behavior is intended?

Should a container be created to serve as the vm that passes down the props? For example, the design would change to this:

// before
<parent-component>
  <child-component />
</parent-component>

// after
<vm-container>
   <parent-component>
     <child-component />
   </parent-component>
<vm-container>

But the above example seems unnecessarily bloated since the essence of the parent component already entailed all the data it needed to provide to the child. But I'm open to discussion; is this what you guys suggest?

the vm-container will not change anything because the slot is rendered in app context

By "technical requirement" does that mean that there is no workaround or that the behavior is intended?

The behaviour is a result of the way functional components work. Consider this set of components:

<!-- template of a `parent` component -->
<template>
  <Child>
    <functional />
  </Child>
</template>

When you pass a functional component into another component's slot, it has to be rendered befor it is passed to the child, so that that child component can receive the resulting vNodes as the slot content. (*)

In the context of my example above, that means that at the moment that the <functional> component renders, the available parent is the outer component (<parent>), not the <child>.

Consequently, the only injections available to the functional component are those that are available in <parent> as well.


(*): That's just how the current implementaiton of the virtualdom works with functional components. To change that would require quite changing quite a lot of internal mechanics.

@posva @LinusBorg Got it, thanks for explaining.

So due to these requirements, the only way to use provide/inject with functional components is to have the props provided in the app context.

I'm sure this constraint will be clarified in the documentation. Please go ahead and close this issue if there isn't anything else that needs to be done or clarified; thanks again!

Maybe we can find a way to improve functional component in slot
But at the moment, it should be done like @posva and @LinusBorg said

@Kingwl Thanks for keeping this open.

I finally had some time to try to incorporate this into my vue-mobiledoc-editor plugin using the above advice. One problem that I foresee if the component needs to be used from the app instance, is that it's more difficult to allow flexibility with the nested components used.

For example, I have to export the components already registered under the app instance:

...

export default Vue.extend({
  render (h) {
    return (
      <div>
        <ParentComp>
          <ChildFuncComp/>
        </ParentComp>
      </div>
    )
  },

  provide () { // data that needs to be injected into functional components 
    return {
       msg: 'hello'
    }
  },

  components: {
    ParentComp,
    ChildFuncComp
  }
})

Then from my understanding, when the user is using the plugin, it would be like so:

// template
<div id="app">
   <div id="#someWhereInApp" />
</div>

// script 
import SuperCoolComponent from 'SuperCoolComponent' 

export default {
   mounted () {
    this.$once('mounted', () => new SuperCoolComponent().$mount('#someWhereInApp'))
    this.$emit('mounted')
  }
}

If my implementation is correct then this severely limits the usage of provide/inject with functional components since you're not allowed to individually import and register the components you wish to use.

I would use full components instead to support the provide/inject

i'm trying to resolve this
maybe It is a long process🌚

@Kingwl Were you able to resolve it?

Are there plans to address this issue in v3?

Eg I am trying to abstract a v-for away into a render function but my childs can be functional components (so they are already rendered when entering the render function and I cant clone them).

Any update?

Was this page helpful?
0 / 5 - 0 ratings

Related issues

jiankafei picture jiankafei  ·  5Comments

kucherenkovova picture kucherenkovova  ·  7Comments

Aaron-Bird picture Aaron-Bird  ·  5Comments

MyBeta picture MyBeta  ·  4Comments

mattlavallee picture mattlavallee  ·  3Comments