Running method of component child from this.props.children array - javascript

import React from "react";
import ReactDOM from "react-dom";
class NestedComponent extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method one ran");
}
render() {
return <div>NestedComponent</div>;
}
}
class NestedComponentTwo extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method two ran");
}
render() {
return <div>NestedComponentTwo</div>;
}
}
class WrappingComponent extends React.Component {
constructor(props) {
super(props);
this.runMethod = this.runMethod.bind(this);
}
runMethod() {
let child = this.props.children[0];
/** Always returns as undefined */
//if (typeof child.childMethod == "function") {
// child.childMethod();
//}
/**
* EDIT: Close, however the this binding seems to not be working. I can however provide the childs props to the childMethod and work with that.
*/
if(typeof child.type.prototype.childMethod == "funciton"){
child.type.prototype.childMethod();
}
}
render() {
return (
<div>
{this.props.children}
<button onClick={this.runMethod}>run</button>
</div>
);
}
}
const App = ({}) => {
return (
<div>
<WrappingComponent>
<NestedComponent />
<NestedComponentTwo />
</WrappingComponent>
</div>
);
};
if (document.getElementById("example")) {
ReactDOM.render(<App />, document.getElementById("example"));
}
So the goal is to have optional methods attached to a nested component that can execute from the wrapping component, almost like an event emmiter. For some reason though, the method that exists on the child component claims not to exist. However whenever I log the child component pulled from the array of the this.props.children the prototype has the method listed.
Am I missing a special way to access methods of children components through a methods variable perhaps?

Found the variable I can use to access it. If anyone has any more insight into this, or reasons why what I am doing is poor practice please let me know.
Editing the question where this is needed, but the item below is accessing the function of the child:
child.type.prototype.childMethod
Does not appear to maintain the this binding. Passing props down does work however.

You should manage all of this logic in the top level component (the App component)
class NestedComponent extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method one ran");
}
render() {
return <div>NestedComponent</div>;
}
}
class NestedComponentTwo extends React.Component {
constructor(props) {
super(props);
this.childMethod = this.childMethod.bind(this);
}
childMethod() {
alert("Child method two ran");
}
render() {
return <div>NestedComponentTwo</div>;
}
}
class WrappingComponent extends React.Component {
render() {
return (
<div>
{this.props.children}
<button onClick={this.props.onClick}>run</button>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.runMethod = this.runMethod.bind(this);
}
runMethod() {
if (this.nestedComponent) {
this.nestedComponent.childMethod();
}
}
render() {
return (
<div>
<WrappingComponent onClick={this.runMethod}>
<NestedComponent ref={el => this.nestedComponent = el} />
<NestedComponentTwo />
</WrappingComponent>
</div>
);
}
};
if (document.getElementById("example")) {
ReactDOM.render(<App />, document.getElementById("example"));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="example"></div>
Moreover ref with string attribute is deprecated https://reactjs.org/docs/refs-and-the-dom.html#legacy-api-string-refs

Related

Am I not allowed to pass props from outside of the map function?

I'm building an app with React, and getting a TypeError that my function doesn't exist. My function starts in here where I pass it to a child component:
class InvoiceScreen extends Component {
state = {
numberOfInvoices: InvoiceData.length,
currentDisplay: <InvoiceList
openInvoice={this.openInvoice}
/>
};
checkInvoiceLength = () => {
var isEmpty = document.getElementById("InvoiceList").innerHTML === "";
if (isEmpty == false) {
this.setState({display: "untoggled"})
}
else if (isEmpty == true) {
this.setState({hasInvoices: "toggled"})
}
}
openInvoice = (int) => {
this.setState({currentDisplay:
<InvoiceDetails
idNumber={InvoiceData[int].id}
description={InvoiceData[int].description}
street={InvoiceData[int].senderAddress.street}
city={InvoiceData[int].senderAddress.city}
postCode={InvoiceData[int].senderAddress.postCode}
country={InvoiceData[int].senderAddress.country}
createdAt={InvoiceData[int].createdAt}
paymentDue={InvoiceData[int].paymentDue}
clientStreet={InvoiceData[int].clientAddress.street}
clientCity={InvoiceData[int].clientAddress.city}
clientPostCode={InvoiceData[int].clientAddress.postCode}
clientCountry={InvoiceData[int].clientAddress.country}
clientEmail={InvoiceData[int].clientEmail}
items={InvoiceData[int].items}
total={InvoiceData[int].total}
/>})
}
render() {
return(
<div className="InvoiceScreen">
<IconBar />
<div className="DisplayArea">
{this.state.currentDisplay}
</div>
</div>
)
}
}
Then from this component, I pass it down to multiple components created through the map function
class InvoiceList extends Component {
constructor(props) {
super(props)
}
render() {
return(
<div className="InvoiceListScreen">
<InvoiceOptions numberOfInvoices={this.props.numberOfInvoices} />
<div id="InvoiceList">
{InvoiceData.map((invoice, index,) =>
<InvoiceBar
openInvoice={this.props.openInvoice}
key={index}
position={index}
idNumber={invoice.id}
clientName={invoice.clientName}
paymentDue={invoice.paymentDue}
price={Formatter.format(invoice.total)}
status={invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1).toLowerCase()}
/>
)}
</div>
</div>
)
}
}
And then finally inside of the mapped components, I call it as an onClick
class InvoiceBar extends Component {
constructor(props) {
super(props)
}
render() {
return(
<div className="InvoiceBar" onClick={() => this.props.openInvoice(this.props.position)}>
<h4 className="idNumber"><span className="Hashtag">#</span>{this.props.idNumber}</h4>
<p className="clientName">{this.props.clientName}</p>
<div className="DueAndPrice">
<p className="paymentDue">Due {this.props.paymentDue}</p>
<h3 className="price">{this.props.price}</h3>
</div>
<PaymentStatus status={this.props.status} />
</div>
)
}
}
And then like I said, I'm given a TypeError saying that it isn't a function. I'm wondering if it has something to do with the function being passed as props from outside of the map function in the second component. Can someone please enlighten me on what it is I'm doing wrong?
The problem is that class fields run in order in which they're listed. They're not like normal methods, which get defined on the prototype ahead of time. For a simplified version:
class InvoiceScreen extends Component {
state = {
openInvoice: this.openInvoice
};
openInvoice = () => {
// some function
}
desugars to:
class InvoiceScreen extends Component {
constructor(props) {
super(props);
this.state = {
openInvoice: this.openInvoice
};
this.openInvoice = () => {
// some function
}
See the problem? You're defining this.state before you're defining this.openInvoice.
Easiest solution would be to move the definition of this.state to the bottom:
class InvoiceScreen extends Component {
// PUT ALL OTHER METHOD DEFINITIONS HERE
// then just before the end of the component:
state = {
numberOfInvoices: InvoiceData.length,
currentDisplay: <InvoiceList
openInvoice={this.openInvoice}
/>
};
}
That said, putting a React component into state is really, really weird. Consider a different approach if at all possible.

React parent-child communication: TypeError: _this.state.myFunction is not a function

I'm new to the world of React and I'm trying to build a parent component with a function that should be invoked from a child component. However, when I call the function, I get the error message in the title. I have something similar:
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.myFunction = this.myFunction.bind(this);
}
myFunction(param) {
//do something
}
render(){
return(
<ChildComponent event={this.myFunction} />
);
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
inheritedFunction: this.props.event
};
}
childFunction(param) {
//do a few things first
this.state.inheritedFunction(param);
}
render(){
return(
<input type="checkbox" onChange={this.childFunction.bind(this)></input>
);
}
}
My code compiles and runs, and then when it gets to execute the childFunction( ) upon selecting the checkbox, this.state.inheritedFunction(param) says that it is not a function and the application collapses. I suspect that it has to do something with binding, but I'm really not sure and stuck with this problem.
I'm new to React, so please be nice. :-) Anyone knows what I messed up?
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.myFunction = this.myFunction.bind(this);
}
myFunction = (param) => {
//do something
alert(param);
}
render(){
return(
<ChildComponent event={this.myFunction} />
);
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
inheritedFunction: this.props.event
};
}
childFunction(param) {
//do a few things first
this.props.event(param);
}
render(){
return(
<input type="checkbox" onChange={this.childFunction.bind(this)></input>
);
}
}
I think your problem is trying to save a reference inside the state and then on the onchange callback you are binding again the function.
remove the state on the constructor and just call it directly from props
class ParentComponent extends React.Component {
constructor(props) {
super(props);
this.myFunction = this.myFunction.bind(this);
}
myFunction(param) {
//do something
}
render(){
return(
<ChildComponent event={this.myFunction} />
);
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.childFunction = this.childFunction.bind(this); // bind on the constructor.
}
childFunction() {
//do a few things first
this.props.event(this) //call the prop directly here.
}
render(){
return(
<input type="checkbox" onChange={this.childFunction></input> // no more binding here
);
}
}
class ParentComponent extends React.Component {
constructor(props) {
super(props);
}
myFunction = param => {
//do something
};
render() {
return <ChildComponent event={this.myFunction} />;
}
}
class ChildComponent extends React.Component {
constructor(props) {
super(props);
}
childFunction = (param) => {
//do a few things first
this.props.event(param);
}
render() {
return <input type="checkbox" onChange={this.childFunction} />
}
}

Extends state react

I need to inherit the state. Can I inherit the state? When I do this, I get an empty state.
class Example extends Component {
constructor(props) {
super();
this.state = {
param1:
};
}
...
}
class Example2 extends Example {
render() {
return (
{this.state.param1} // empty
)
}
}
You can extend state as follows:
constructor(props) {
super(props)
this.state = {
...this.state,
extraStuff: '',
}
}
Instead of using inheritance, you could use regular composition and pass the entire Example state as props to Example2 and use the props passed to Example2 as initial state.
Example
class Example extends React.Component {
state = {
param1: "test"
};
render() {
return <Example2 {...this.state} />;
}
}
class Example2 extends React.Component {
state = {...this.props};
render() {
return <div>{JSON.stringify(this.state)}</div>;
}
}
ReactDOM.render(<Example />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
class Example extends Component {
constructor(props) {
super();
this.state = {
param1: "param1"
};
}
render() {
const { param1 } = this.state;
return (
<Example2 param1={param1} />
)
}
}
class Example2 extends Example {
render() {
const { param1 } = this.props;
return (
{param1}
)
}
}
Kovich. You can pass the state of one component to another only as props like this code:
class Example extends Component {
constructor(props) {
super();
this.state = {
param1:
};
}
render(){
return (<Example2 param1 ={this.state.param1} />)
}
...
}
class Example2 extends Example {
constructor(props) {
super();
this.state = {
param1: this.props.param1
};
}
render() {
return (
{this.state.param1} // empty
)
}
}
Inheritance is not really supported in React, you can find more information on this link:
https://reactjs.org/docs/composition-vs-inheritance.html#so-what-about-inheritance
What you can do is to have a global state manager (for example Redux) or pass the state to the Example2 component on the following way:
class Example extends Component {
constructor(props) {
super(props);
this.state = {
param1: 'test'
};
render() {
return (
<Example2 state={this.state} />
);
}
}
After that you will be able to reach it in Example2 by this.props.state.param1.

how to attach ref to compose component

I am currently trying to get the child component functions using the ref in the following way but it doesn't show any of the details.
class ChildCompent extends React.Component {
constructor(props){
super(props);
}
_function1 =()=>{
/* ...... */
}
_function1 =()=>{
/* ...... */
}
render (){
return (
<div>
ChildComponent
</div>
)
}
}
let ComposedChild = compose(
/* --- graphql query */
)(ChildComponent);
class ParentComponent extends React.Component {
constructor(props){
super(props);
}
_onClick = ()=>{
console.log(this.refs.childComponent)
// doesn't show the _function1 and _function2
}
render (){
return (
<div onClick={this._onClick}>
<div>Testing</div>
<ChildComponent ref="childComponent"/>
</div>
)
}
}
You should make use of ref callback to set ref on a component
See this answer:
How to access a DOM element in React? What is the equilvalent of document.getElementById() in React
After that to access another component's function you can do that like this.childComponent._function1()
Also you need to make use of the component returned after wrapping it with the compose function in the ParentComponent.
class ChildComponent extends React.Component {
constructor(props){
super(props);
}
_function1 =()=>{
/* ...... */
console.log('hello');
}
render (){
return (
<div>
ChildComponent
</div>
)
}
}
let ComposedChild = compose(
/* --- graphql query */
)(ChildComponent);
class ParentComponent extends React.Component {
constructor(props){
super(props);
}
_onClick = ()=>{
this.childComponent._function1()
// doesn't show the _function1 and _function2
}
render (){
return (
<div onClick={this._onClick}>
<div>Testing</div>
<ComposedChild ref={(ref) => this.childComponent = ref}/>
</div>
)
}
}

What does "Warning: setState(...): Can only update a mounted or mounting component" mean?

So I have one root component and two child components. I have trying to get one child to call a method that is up in in the root component and update the state up in the root component, and pass the updated down to the other component, but I am getting the following error.
What could be the issue?
warning.js?8a56:36 Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the firstChild component.
Here is the code:
firstChild.js
export default class firstChild extends React.Component {
constructor(props) {
super(props);
this.state = {
nameText: '',
}
}
nameChange(event) {
this.setState({
nameText: event.target.value,
})
}
submitClick() {
var nameText = this.state.nameText;
this.props.saveName(nameText)
this.setState({nameText: ''});
}
render() {
var st = this.state;
var pr = this.props;
return (
<input
placeholder='Enter Name'
onChange={this.nameChange.bind(this)}
value={this.state.nameText}
/>
<button
onClick={this.submitClick.bind(this)}
/>
And in root component, App.js:
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
submitSuccess: false
}
}
saveName(nameText) {
this.setState({submitSuccess: true});
}
render() {
var props = {};
props.submitSuccess = this.state.submitSuccess;
return (
<div>
<firstChild
saveName={this.saveName.bind(this)}
/>
{React.Children.map(this.props.children, function(child) {
return React.cloneElement(child, props);
})}
</div>
)
}
}
And my secondChild.js:
export default class secondChild extends React.Component {
static propTypes = {
submitSuccess: React.PropTypes.bool.isRequired,
}
constructor(props) {
super(props);
this.state = {
}
}
render() {
return (
<div>
{this.props.submitSuccess}
</div>
)
}
}
Fisrt, rename all your React components as Camel Case like this.
class firstChild ... --> class FristChild
<fristChild> --> <FristChild>
Second, in your FirstChild render method, you should wrap your elements into an enclosing tag like this:
class FirstChild extends Component {
render(){
return (
<div>
<input ... />
<button ... />
</div>
)
}
}
Third, when you use cloneElement upon this.props.children, you should use Proptypes.<type> in your secondChildren instead of Propstypes.<type>.isRequired. Check it here to see why.
class SecondChild extends Component {
static propTypes = {
submitSuccess: React.PropTypes.bool, // remove isRequired
}
}
Regardless all above, I have tested your code and it works fine.
You can try and use componentWillUnmount lifecycle function in order to check when the component is unmounted.
You can also use a flag to signal that the component is unmounted before setting the state:
saveName(nameText) {
if (!this.isUnmounted){
this.setState({submitSuccess: true});
}
}
componentWillUnmount() {
this.isUnmounted = true;
}

Categories