Pass properties from parent component to all transcluded children component in Vue - javascript

I would like to pass some properties from a parent to all of his children when those are transcluded (content distribution syntax). In this case, the parent doesen't know (as far as I know) his children, so I don't know how to proceed.
More specificly, I want a way to write this :
<my-parent prop1="foo" prop2="bar">
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
<my-children></my-children> <!-- Must know content of prop1 and prop2 -->
</my-parent>
Instead of having to write this :
<my-parent prop1="foo" prop2="bar">
<my-children prop1="foo" prop2="bar"></my-children>
<my-children prop1="foo" prop2="bar"></my-children>
</my-parent>
Is it possible ? Thanks.

Props allow data flow only one level. If you want to perpetuate data, you can use an event bus instead.
Instantiate an event bus with an empty Vue instance in your main file.
var bus = new Vue();
Then in your parent, emit the event with data to be passed
bus.$emit('myEvent', dataToBePassed);
Listen for myEventanywhere you want to pick up the data. In your case, it is done in your child components
bus.$on('myEvent', function(data) {
.....
});

Here is my solution, that's probably not a great deal, but that's the cleanest solution for what I want to do right now. The principle is to create computed properties that will use own component prop if they exist, or get $parent values otherwise. The real prop would then be accessible in this._prop.
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
computed: {
_prop1: function() {
return this.prop1 || this.$parent.prop1;
},
_prop2: function() {
return this.prop2 || this.$parent.prop2;
}
}
});
Here is a mixin generator that does that in a more elegant way, and with, possibly, multiple levels :
function passDown(...passDownProperties) {
const computed = {};
passDownProperties.forEach((prop) => {
computed["_" + prop] = function() {
return this[prop] || this.$parent[prop] || this.$parent["_" + prop];
};
});
return { computed };
}
Vue.component('my-children', {
props: ["prop1", "prop2"],
template: "<div>{{_prop1}} - {{_prop2}}</div>",
mixins: [passDown("prop1", "prop2")]
});

At this point (I'm not a vue expert) I just could think in this solution.
Assign every component's props is boring I agree, so why not doing it programmatically?
// Create a global mixin
Vue.mixin({
mounted() { // each component will execute this function after mounted
if (!this.$children) {
return;
}
for (const child of this.$children) { // iterate each child component
if (child.$options._propKeys) {
for (const propKey of child.$options._propKeys) { // iterate each child's props
// if current component has a property named equal to child prop key
if (Object.prototype.hasOwnProperty.call(this, propKey)) {
// update child prop value
this.$set(child, propKey, this[propKey]);
// create a watch to update value again every time that parent property changes
this.$watch(propKey, (newValue) => {
this.$set(child, propKey, newValue);
});
}
}
}
}
},
});
This works but you will get an ugly vue warn message:
[Vue warn]: Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value.
I'm not sure if this is a good solution but it works, so if you decide to use just keep in mind Global-Mixin recomendations:
Use global mixins sparsely and carefully, because it affects every
single Vue instance created, including third party components.
Please see a full example at https://github.com/aldoromo88/PropsConvention
Hope it helps

Related

Accidentally updating child variables

I'm currently working in a page with parent/child components. Somehow my child component gets updated when I manage its variables in the parent component.
What I'm trying to do:
My child component has a 'site' variable with all the data i need to send via API
My parent component has a Save button to send the child data to the Back-end
When 'site' changes in the child component, I'm emitting an event #change to the parent component
The #change event contains all the data I need, but not in the format I want
There is a function submit() that gets this data and modify the one of the arrays so that this: ['foo','bar'] becomes this 'foo,bar'
The problem when I do the step '5' my child component gets updated
The child component inside the parent component
<configuracoes :configuracoes="configuracoes" #change="setData"
v-if="currentPage === 'configs'"/>
The change event emitted by the child component
this.$emit("change", this.site);
The important part of 'site' var
site: {
seo: {
keywords: [],
...
},
...
},
The setData() function
setData(data) {
this.data = data;
},
The submitData() function
submitData() {
if (this.currentPage === "configs") {
let data = ({}, Object.assign(this.data))
let keywords = data.seo.keywords.join(',')
data.seo.keywords = keywords
this.$store.dispatch("sites/updateSite", {
empresa_id: this.user.empresa_id,
site_id: this.siteId,
dados: data,
});
}
}
As you can see, I'm declaring another variable let data to avoid updating this.site variable, but no success
First of all, there is an issue with how you're "copying" your this.data object.
let data = ({}, Object.assign(this.data)); // this doesn't work
console.log(data === this.data); // true
const dataCopy = Object.assign({}, this.data); // this works
console.log(dataCopy === this.data); // false
The way Object.assign works, all the properties will be copied over into the first argument. Since you only pass a single argument, it doesn't change and is still pointing to the same old object.
If you use the correct way, you will most likely still run into the same issue. The reason is that data.seo is not a primitive value (a number or a string), but is an object.
This means that the whole seo object will be copied over into the new copy. In other words, even though dataCopy !== this.data, dataCopy.seo === this.data.seo. This is known as "shallow copy".
You want to make sure you DO NOT modify the original seo object, here are a few ways to do that.
let goodCopy;
const newKeywords = this.data.seo.keywords.join(',');
// use object spread syntax
goodCopy = {
...this.data,
seo: {
...this.data.seo,
keywords: newKeywords,
},
};
// use Object.assign
goodCopy = Object.assign(
{},
this.data,
{
seo: Object.assign(
{},
this.data.seo,
{keywords: newKeywords}),
});
// create a copy of "seo", and then change it to your liking
const seoCopy = {...this.data.seo};
seoCopy.keywords = newKeywords;
goodCopy = Object.assign({}, this.data, {seo: seoCopy});
this.$store.dispatch('sites/updateSite', {
empresa_id: this.user.empresa_id,
site_id: this.siteId,
dados: goodCopy,
});
If you want to read up on ways to copy a JavaScript object, here's a good question.

How to rerun a Vuex Getter

Maybe I have misunderstood what a getter is in Vuex, but say I have a getter that gets the size of a DOM element, a div for example. I would do something like this :
const getters = {
getContainerWidth (state) {
return document.getElementById('main-content').clientWidth;
}
}
Now when I start my app, all the getters seem to be run straight away. What if the div isn't available at startup? How do I rerun a getter?
I run the getter like this at the moment :
import store from '#/store'
store.getters['myModule/getContainerWidth']
I thought maybe this would work :
store.getters['myModule/getContainerWidth']()
But since store.getters is an object containing properties and values, and the values not being functions, I can't rerun them.
Any ideas?
Getters should depend on state field to be reactive. It you want to observe clientWidth changes - it does not work.
If you want to use it like function then just return function from getter:
const getters = {
getContainerWidth (state) {
return () => {
let container = document.getElementById('main-content');
return container ? container.clientWidth : 0
};
}
}
and use it like getContainerWidth()

Vue.js: check if a component exists

I need to do some stuff in the ready: of the root instance only when some components don't exist (they weren't declared in the HTML).
How can I check if a component exists?
We can get the list of the loaded (global and/or local) components of a Vue instance from the instantiation options which is available through the vm.$options where the vm is the current Vue instance.
vm.$options property holds the whole options of a Vue instance. For example vm.$options.components returns an object containing the loaded components of the current Vue instace vm.
However depending on the way how a component is registered, either globally through Vue.component() or locally within a Vue instance options, the structure of the vm.$options.components would be different.
If the component is registered globally, the component will be added to vm.$options.components [[Prototype]] linkage or its __proto__.
And if the component is registered locally within the Vue instance options, the component will be added to the vm.$options.components object directly as its own property. So that we do not have to walk the proto chain to find the component.
In the following example we will see how to access the loaded components in both situations.
Notice the // [1] and // [2] comments in the code which are related to local registered components.
// the globally registered component
Vue.component('global-child', {
template: '<h2>A message from the global component</h2>'
});
var localComponent = Vue.extend({ template: '<h2>A message from the local component</h2>' });
// the root view model
new Vue({
el: 'body',
data: {
allComponents: [],
localComponents: [],
globalComponentIsLoaded: false
},
// local registered components
components: { // [1]
'local-child': localComponent
},
ready: function() {
this.localComponents = Object.keys(this.$options.components); // [2]
this.allComponents = loadedComponents.call(this);
this.globalComponentIsLoaded = componentExists.call(this, 'global-child');
}
});
function loadedComponents() {
var loaded = [];
var components = this.$options.components;
for (var key in components) {
loaded.push(key);
}
return loaded;
}
function componentExists(component) {
var components = loadedComponents.call(this);
if (components.indexOf(component) !== -1) {
return true;
}
return false;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.24/vue.js"></script>
<h1>A message from the root Vue instance</h1>
<p>All loaded components are: {{ allComponents | json }}</p>
<p>local components are: {{ localComponents | json }}</p>
<p><code><global-child></code> component is loaded: <strong>{{ globalComponentIsLoaded }}</strong></p>
<global-child></global-child>
<local-child></local-child>
I'm not sure if you need a dynamic method, but this may help to determine if a component exists:
Vue.options.components['CompOne']
found:
https://github.com/vuejs/Discussion/issues/140
To check if a global component exists:
let componentExists = 'componentName' in Vue.options.components
To check if a imported component exists in a component:
let componentExists = 'componentName' in this.$options.components
get the current component imported components
this.$options.components[findComponentName]
get the global components
Vue.$options.components[findComponentName]
var isComponentExists = "component-name" in Vue.options.components
I really hope there is a better answer than this, but for the moment this solves the problem.
In the ready, I access the children elements through this (could be also Vue) and check whether their name is or not what I was expecting:
ready: function() {
for (var i = 0; i < this.$children.length; i++) {
if (
this.$children[i].$options.name == 'my_component_a'
|| this.$children[i].$options.name == 'my_component_b'
|| this.$children[i].$options.name == 'my_component_c'
) {
//do stuff
}
}
}
You can also access them directly if you previously assigned them a reference in their template:
//template:
<comp v-ref:my_component_ref></comp>
Then from the Vue component ready:
if (this.$refs.my_component_ref){
//do stuff
}

How to access one component's state from another component

How do I access one component's state in another component? Below is my code and I'm trying to access the state of component a in component b.
var a = React.createClass({
getInitialState: function () {
return {
first: "1"
};
},
render: function () {
// Render HTML here.
}
});
var b = React.createClass({
getInitialState: function () {
return {
second: a.state.first
};
},
render: function () {
// Render HTML here.
}
});
But I'm not getting anything.
Even if you try doing this way, it is not correct method to access the state. Better to have a parent component whose children are a and b. The ParentComponent will maintain the state and pass it as props to the children.
For instance,
var ParentComponent = React.createClass({
getInitialState : function() {
return {
first: 1,
}
}
changeFirst: function(newValue) {
this.setState({
first: newValue,
});
}
render: function() {
return (
<a first={this.state.first} changeFirst={this.changeFirst.bind(this)} />
<b first={this.state.first} changeFirst={this.changeFirst.bind(this)} />
)
}
}
Now in your child compoenents a and b, access first variable using this.props.first. When you wish to change the value of first call this.props.changeFirst() function of the ParentComponent. This will change the value and will be thus reflected in both the children a and b.
I am writing component a here, b will be similar:
var a = React.createClass({
render: function() {
var first = this.props.first; // access first anywhere using this.props.first in child
// render JSX
}
}
If two components need access to the same state they should have a common ancestor where the state is kept.
So component A is the parent of B and C.
Component A has the state, and passes it down as props to B and C.
If you want to change the state from B you pass down a callback function as a prop.
I would suggest you use a state manager like Redux (personal favorite), MobX reflux, etc to manage your state.
How these works is they allow you to contain all shared state in one state storage (called a store), and whatever component needs access to a part of that shared state, it will just get it from the store.
It looked very hard to get started with but once you get over the small challenges, get 2 or 3 "wtf's" out of the way. It gets easier.
Take a look here: http://redux.js.org/
EDIT: Redux is good but the boilerplate code is really a turn off... for those of you looking for a simpler, more magical (this can be good and bad) solution use mobx : https://mobx.js.org/
in the child component create function that sets the state:
changeTheState(){
this.setState({something:"some value"})
}
and in parent component give the child a ref as following:
<Child ref={component => this._child = component}/>
then in parent make a function to access the changeTheState()
parentFunction(){
this._child.changeTheState();
}
and just use the parentFunction.
If you have A and B component where B is a child of A, you can pass a function to change the state of A though props to B.
function B(props) {
return <button onClick={props.changeA} />
}
class A extends React.Component {
//constructor
//pass this function to B to change A's state
handleA() {
this.setState({});
}
render() {
return <B changeA={() => this.handleA()} />
}
}
Take a look at React Context
Context provides a way to pass data through the component tree without having to pass props down manually at every level.
You can also update Context from a nested component if required.

Pass props to parent component in React.js

Is there not a simple way to pass a child's props to its parent using events, in React.js?
var Child = React.createClass({
render: function() {
<a onClick={this.props.onClick}>Click me</a>
}
});
var Parent = React.createClass({
onClick: function(event) {
// event.component.props ?why is this not available?
},
render: function() {
<Child onClick={this.onClick} />
}
});
I know you can use controlled components to pass an input's value but it'd be nice to pass the whole kit n' kaboodle. Sometimes the child component contains a set of information you'd rather not have to look up.
Perhaps there's a way to bind the component to the event?
UPDATE – 9/1/2015
After using React for over a year, and spurred on by Sebastien Lorber's answer, I've concluded passing child components as arguments to functions in parents is not in fact the React way, nor was it ever a good idea. I've switched the answer.
Edit: see the end examples for ES6 updated examples.
This answer simply handle the case of direct parent-child relationship. When parent and child have potentially a lot of intermediaries, check this answer.
Other solutions are missing the point
While they still work fine, other answers are missing something very important.
Is there not a simple way to pass a child's props to its parent using events, in React.js?
The parent already has that child prop!: if the child has a prop, then it is because its parent provided that prop to the child! Why do you want the child to pass back the prop to the parent, while the parent obviously already has that prop?
Better implementation
Child: it really does not have to be more complicated than that.
var Child = React.createClass({
render: function () {
return <button onClick={this.props.onClick}>{this.props.text}</button>;
},
});
Parent with single child: using the value it passes to the child
var Parent = React.createClass({
getInitialState: function() {
return {childText: "Click me! (parent prop)"};
},
render: function () {
return (
<Child onClick={this.handleChildClick} text={this.state.childText}/>
);
},
handleChildClick: function(event) {
// You can access the prop you pass to the children
// because you already have it!
// Here you have it in state but it could also be
// in props, coming from another parent.
alert("The Child button text is: " + this.state.childText);
// You can also access the target of the click here
// if you want to do some magic stuff
alert("The Child HTML is: " + event.target.outerHTML);
}
});
JsFiddle
Parent with list of children: you still have everything you need on the parent and don't need to make the child more complicated.
var Parent = React.createClass({
getInitialState: function() {
return {childrenData: [
{childText: "Click me 1!", childNumber: 1},
{childText: "Click me 2!", childNumber: 2}
]};
},
render: function () {
var children = this.state.childrenData.map(function(childData,childIndex) {
return <Child onClick={this.handleChildClick.bind(null,childData)} text={childData.childText}/>;
}.bind(this));
return <div>{children}</div>;
},
handleChildClick: function(childData,event) {
alert("The Child button data is: " + childData.childText + " - " + childData.childNumber);
alert("The Child HTML is: " + event.target.outerHTML);
}
});
JsFiddle
It is also possible to use this.handleChildClick.bind(null,childIndex) and then use this.state.childrenData[childIndex]
Note we are binding with a null context because otherwise React issues a warning related to its autobinding system. Using null means you don't want to change the function context. See also.
About encapsulation and coupling in other answers
This is for me a bad idea in term of coupling and encapsulation:
var Parent = React.createClass({
handleClick: function(childComponent) {
// using childComponent.props
// using childComponent.refs.button
// or anything else using childComponent
},
render: function() {
<Child onClick={this.handleClick} />
}
});
Using props:
As I explained above, you already have the props in the parent so it's useless to pass the whole child component to access props.
Using refs:
You already have the click target in the event, and in most case this is enough.
Additionnally, you could have used a ref directly on the child:
<Child ref="theChild" .../>
And access the DOM node in the parent with
React.findDOMNode(this.refs.theChild)
For more advanced cases where you want to access multiple refs of the child in the parent, the child could pass all the dom nodes directly in the callback.
The component has an interface (props) and the parent should not assume anything about the inner working of the child, including its inner DOM structure or which DOM nodes it declares refs for. A parent using a ref of a child means that you tightly couple the 2 components.
To illustrate the issue, I'll take this quote about the Shadow DOM, that is used inside browsers to render things like sliders, scrollbars, video players...:
They created a boundary between what you, the Web developer can reach
and what’s considered implementation details, thus inaccessible to
you. The browser however, can traipse across this boundary at will.
With this boundary in place, they were able to build all HTML elements
using the same good-old Web technologies, out of the divs and spans
just like you would.
The problem is that if you let the child implementation details leak into the parent, you make it very hard to refactor the child without affecting the parent. This means as a library author (or as a browser editor with Shadow DOM) this is very dangerous because you let the client access too much, making it very hard to upgrade code without breaking retrocompatibility.
If Chrome had implemented its scrollbar letting the client access the inner dom nodes of that scrollbar, this means that the client may have the possibility to simply break that scrollbar, and that apps would break more easily when Chrome perform its auto-update after refactoring the scrollbar... Instead, they only give access to some safe things like customizing some parts of the scrollbar with CSS.
About using anything else
Passing the whole component in the callback is dangerous and may lead novice developers to do very weird things like calling childComponent.setState(...) or childComponent.forceUpdate(), or assigning it new variables, inside the parent, making the whole app much harder to reason about.
Edit: ES6 examples
As many people now use ES6, here are the same examples for ES6 syntax
The child can be very simple:
const Child = ({
onClick,
text
}) => (
<button onClick={onClick}>
{text}
</button>
)
The parent can be either a class (and it can eventually manage the state itself, but I'm passing it as props here:
class Parent1 extends React.Component {
handleChildClick(childData,event) {
alert("The Child button data is: " + childData.childText + " - " + childData.childNumber);
alert("The Child HTML is: " + event.target.outerHTML);
}
render() {
return (
<div>
{this.props.childrenData.map(child => (
<Child
key={child.childNumber}
text={child.childText}
onClick={e => this.handleChildClick(child,e)}
/>
))}
</div>
);
}
}
But it can also be simplified if it does not need to manage state:
const Parent2 = ({childrenData}) => (
<div>
{childrenData.map(child => (
<Child
key={child.childNumber}
text={child.childText}
onClick={e => {
alert("The Child button data is: " + child.childText + " - " + child.childNumber);
alert("The Child HTML is: " + e.target.outerHTML);
}}
/>
))}
</div>
)
JsFiddle
PERF WARNING (apply to ES5/ES6): if you are using PureComponent or shouldComponentUpdate, the above implementations will not be optimized by default because using onClick={e => doSomething()}, or binding directly during the render phase, because it will create a new function everytime the parent renders. If this is a perf bottleneck in your app, you can pass the data to the children, and reinject it inside "stable" callback (set on the parent class, and binded to this in class constructor) so that PureComponent optimization can kick in, or you can implement your own shouldComponentUpdate and ignore the callback in the props comparison check.
You can also use Recompose library, which provide higher order components to achieve fine-tuned optimisations:
// A component that is expensive to render
const ExpensiveComponent = ({ propA, propB }) => {...}
// Optimized version of same component, using shallow comparison of props
// Same effect as React's PureRenderMixin
const OptimizedComponent = pure(ExpensiveComponent)
// Even more optimized: only updates if specific prop keys have changed
const HyperOptimizedComponent = onlyUpdateForKeys(['propA', 'propB'])(ExpensiveComponent)
In this case you could optimize the Child component by using:
const OptimizedChild = onlyUpdateForKeys(['text'])(Child)
Update (9/1/15): The OP has made this question a bit of a moving target. It’s been updated again. So, I feel responsible to update my reply.
First, an answer to your provided example:
Yes, this is possible.
You can solve this by updating Child’s onClick to be this.props.onClick.bind(null, this):
var Child = React.createClass({
render: function () {
return <a onClick={this.props.onClick.bind(null, this)}>Click me</a>;
}
});
The event handler in your Parent can then access the component and event like so:
onClick: function (component, event) {
// console.log(component, event);
},
JSBin snapshot
But the question itself is misleading
Parent already knows Child’s props.
This isn’t clear in the provided example because no props are actually being provided. This sample code might better support the question being asked:
var Child = React.createClass({
render: function () {
return <a onClick={this.props.onClick}> {this.props.text} </a>;
}
});
var Parent = React.createClass({
getInitialState: function () {
return { text: "Click here" };
},
onClick: function (event) {
// event.component.props ?why is this not available?
},
render: function() {
return <Child onClick={this.onClick} text={this.state.text} />;
}
});
It becomes much clearer in this example that you already know what the props of Child are.
JSBin snapshot
If it’s truly about using a Child’s props…
If it’s truly about using a Child’s props, you can avoid any hookup with Child altogether.
JSX has a spread attributes API I often use on components like Child. It takes all the props and applies them to a component. Child would look like this:
var Child = React.createClass({
render: function () {
return <a {...this.props}> {this.props.text} </a>;
}
});
Allowing you to use the values directly in the Parent:
var Parent = React.createClass({
getInitialState: function () {
return { text: "Click here" };
},
onClick: function (text) {
alert(text);
},
render: function() {
return <Child onClick={this.onClick.bind(null, this.state.text)} text={this.state.text} />;
}
});
JSBin snapshot
And there's no additional configuration required as you hookup additional Child components
var Parent = React.createClass({
getInitialState: function () {
return {
text: "Click here",
text2: "No, Click here",
};
},
onClick: function (text) {
alert(text);
},
render: function() {
return <div>
<Child onClick={this.onClick.bind(null, this.state.text)} text={this.state.text} />
<Child onClick={this.onClick.bind(null, this.state.text2)} text={this.state.text2} />
</div>;
}
});
JSBin snapshot
But I suspect that’s not your actual use case. So let’s dig further…
A robust practical example
The generic nature of the provided example is a hard to talk about. I’ve created a component that demonstrations a practical use for the question above, implemented in a very Reacty way:
DTServiceCalculator working example
DTServiceCalculator repo
This component is a simple service calculator. You provide it with a list of services (with names and prices) and it will calculate a total the selected prices.
Children are blissfully ignorant
ServiceItem is the child-component in this example. It doesn’t have many opinions about the outside world. It requires a few props, one of which is a function to be called when clicked.
<div onClick={this.props.handleClick.bind(this.props.index)} />
It does nothing but to call the provided handleClick callback with the provided index[source].
Parents are Children
DTServicesCalculator is the parent-component is this example. It’s also a child. Let’s look.
DTServiceCalculator creates a list of child-component (ServiceItems) and provides them with props [source]. It’s the parent-component of ServiceItem but it`s the child-component of the component passing it the list. It doesn't own the data. So it again delegates handling of the component to its parent-component source
<ServiceItem chosen={chosen} index={i} key={id} price={price} name={name} onSelect={this.props.handleServiceItem} />
handleServiceItem captures the index, passed from the child, and provides it to its parent [source]
handleServiceClick (index) {
this.props.onSelect(index);
}
Owners know everything
The concept of “Ownership” is an important one in React. I recommend reading more about it here.
In the example I’ve shown, I keep delegating handling of an event up the component tree until we get to the component that owns the state.
When we finally get there, we handle the state selection/deselection like so [source]:
handleSelect (index) {
let services = […this.state.services];
services[index].chosen = (services[index].chosen) ? false : true;
this.setState({ services: services });
}
Conclusion
Try keeping your outer-most components as opaque as possible. Strive to make sure that they have very few preferences about how a parent-component might choose to implement them.
Keep aware of who owns the data you are manipulating. In most cases, you will need to delegate event handling up the tree to the component that owns that state.
Aside: The Flux pattern is a good way to reduce this type of necessary hookup in apps.
It appears there's a simple answer. Consider this:
var Child = React.createClass({
render: function() {
<a onClick={this.props.onClick.bind(null, this)}>Click me</a>
}
});
var Parent = React.createClass({
onClick: function(component, event) {
component.props // #=> {Object...}
},
render: function() {
<Child onClick={this.onClick} />
}
});
The key is calling bind(null, this) on the this.props.onClick event, passed from the parent. Now, the onClick function accepts arguments component, AND event. I think that's the best of all worlds.
UPDATE: 9/1/2015
This was a bad idea: letting child implementation details leak in to the parent was never a good path. See Sebastien Lorber's answer.
The question is how to pass argument from child to parent component. This example is easy to use and tested:
//Child component
class Child extends React.Component {
render() {
var handleToUpdate = this.props.handleToUpdate;
return (<div><button onClick={() => handleToUpdate('someVar')}>Push me</button></div>
)
}
}
//Parent component
class Parent extends React.Component {
constructor(props) {
super(props);
var handleToUpdate = this.handleToUpdate.bind(this);
}
handleToUpdate(someArg){
alert('We pass argument from Child to Parent: \n' + someArg);
}
render() {
var handleToUpdate = this.handleToUpdate;
return (<div>
<Child handleToUpdate = {handleToUpdate.bind(this)} />
</div>)
}
}
if(document.querySelector("#demo")){
ReactDOM.render(
<Parent />,
document.querySelector("#demo")
);
}
Look at JSFIDDLE
Basically you use props to send information to and from Child and Parent.
Adding to all the wonderful answers, let me give a simple example that explains passing values from child to parent component in React
App.js
class App extends React.Component {
constructor(){
super();
this.handleFilterUpdate = this.handleFilterUpdate.bind(this);
this.state={name:'igi'}
}
handleFilterUpdate(filterValue) {
this.setState({
name: filterValue
});
}
render() {
return (
<div>
<Header change={this.handleFilterUpdate} name={this.state.name} />
<p>{this.state.name}</p>
</div>
);
}
}
Header.js
class Header extends React.Component {
constructor(){
super();
this.state={
names: 'jessy'
}
}
Change(event) {
// this.props.change(this.state.names);
this.props.change('jessy');
}
render() {
return (
<button onClick={this.Change.bind(this)}>click</button>
);
}
}
Main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App.jsx';
ReactDOM.render(<App />, document.getElementById('app'));
Thats it , now you can pass values from your client to the server.
Take a look at the Change function in the Header.js
Change(event) {
// this.props.change(this.state.names);
this.props.change('jessy');
}
This is how you push values into the props from client to the server
Here is a simple 3 step ES6 implementation using function binding in the parent constructor. This is the first way the official react tutorial recommends (there is also public class fields syntax not covered here). You can find all of this information here https://reactjs.org/docs/handling-events.html
Binding Parent Functions so Children Can Call Them (And pass data up to the parent! :D )
Make sure in the parent constructor you bind the function you created in the parent
Pass the bound function down to the child as a prop (No lambda because we are passing a ref to function)
Call the bound function from a child event (Lambda! We're calling the function when the event is fired.
If we don't do this the function will automatically run on load and not be triggered on the event.)
Parent Function
handleFilterApply(filterVals){}
Parent Constructor
this.handleFilterApply = this.handleFilterApply.bind(this);
Prop Passed to Child
onApplyClick = {this.handleFilterApply}
Child Event Call
onClick = {() => {props.onApplyClick(filterVals)}
This is an example without using the onClick event. I simply pass a callback function to the child by props. With that callback the child call also send data back. I was inspired by the examples in the docs.
Small example (this is in a tsx files, so props and states must be declared fully, I deleted some logic out of the components, so it is less code).
*Update: Important is to bind this to the callback, otherwise the callback has the scope of the child and not the parent. Only problem: it is the "old" parent...
SymptomChoser is the parent:
interface SymptomChooserState {
// true when a symptom was pressed can now add more detail
isInDetailMode: boolean
// since when user has this symptoms
sinceDate: Date,
}
class SymptomChooser extends Component<{}, SymptomChooserState> {
state = {
isInDetailMode: false,
sinceDate: new Date()
}
helloParent(symptom: Symptom) {
console.log("This is parent of: ", symptom.props.name);
// TODO enable detail mode
}
render() {
return (
<View>
<Symptom name='Fieber' callback={this.helloParent.bind(this)} />
</View>
);
}
}
Symptom is the child (in the props of the child I declared the callback function, in the function selectedSymptom the callback is called):
interface SymptomProps {
// name of the symptom
name: string,
// callback to notify SymptomChooser about selected Symptom.
callback: (symptom: Symptom) => void
}
class Symptom extends Component<SymptomProps, SymptomState>{
state = {
isSelected: false,
severity: 0
}
selectedSymptom() {
this.setState({ isSelected: true });
this.props.callback(this);
}
render() {
return (
// symptom is not selected
<Button
style={[AppStyle.button]}
onPress={this.selectedSymptom.bind(this)}>
<Text style={[AppStyle.textButton]}>{this.props.name}</Text>
</Button>
);
}
}

Categories