Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provided props are not injected into functional components #5837

Open
alidcast opened this issue Jun 7, 2017 · 16 comments
Open

Provided props are not injected into functional components #5837

alidcast opened this issue Jun 7, 2017 · 16 comments
Assignees
Labels

Comments

@alidcast
Copy link

alidcast commented Jun 7, 2017

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.

@jkzing
Copy link
Member

jkzing commented Jun 7, 2017

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 vue@1.0.16 😅

@Austio
Copy link

Austio commented Jun 7, 2017

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.

source = source.$parent

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.

@Kingwl
Copy link
Member

Kingwl commented Jun 7, 2017

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

@LinusBorg
Copy link
Member

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

@posva
Copy link
Member

posva commented Jun 7, 2017

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

@alidcast
Copy link
Author

alidcast commented Jun 7, 2017

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?

@posva
Copy link
Member

posva commented Jun 7, 2017

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

@LinusBorg
Copy link
Member

LinusBorg commented Jun 7, 2017

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.

@alidcast
Copy link
Author

alidcast commented Jun 7, 2017

@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!

@Kingwl
Copy link
Member

Kingwl commented Jun 7, 2017

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

@alidcast
Copy link
Author

alidcast commented Jun 7, 2017

@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.

@posva
Copy link
Member

posva commented Jun 7, 2017

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

@Kingwl
Copy link
Member

Kingwl commented Jul 17, 2017

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

@Kingwl Kingwl self-assigned this Jul 17, 2017
@alidcast
Copy link
Author

@Kingwl Were you able to resolve it?

@pimlie
Copy link

pimlie commented Jun 26, 2019

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).

@daniele-orlando
Copy link

Any update?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants