I want to use an React Component as a object to make some calculation based on other components before I render / mount them. I have a working solution but I find the part a.type.prototype.calculate.bind(a)() quite dirty.
Here's my current way (also on JSBin):
class ChildComponent extends React.Component {
calculate() {
return this.props.a + this.props.b;
}
render() {
return <span>{this.props.a + this.props.b} </span>
}
}
class ParentComponent extends React.Component {
render() {
var children = [
<ChildComponent a={2} b={3} key="1" />,
<ChildComponent a={1} b={2} key="2" />
];
var children_sorted = children.slice();
children_sorted.sort((a,b) => {
return a.type.prototype.calculate.bind(a)()
- b.type.prototype.calculate.bind(b)()
});
return (
<h1>Children {children} -> {children_sorted}</h1>
);
}
}
ReactDOM.render(
<ParentComponent/>,
document.getElementById('react_example')
);
<div id="react_example"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
Does somebody have a better way to achieve that?
One way that comes to mind is to make calculate static:
static calculate({a, b}) {
return a + b;
}
...and pass props into it:
children_sorted.sort((a,b) => a.type.calculate(a.props) - b.type.calculate(b.props));
Live Example:
class ChildComponent extends React.Component {
static calculate({a, b}) {
return a + b;
}
render() {
return <span>{this.props.a + this.props.b} </span>
}
}
class ParentComponent extends React.Component {
render() {
var children = [
<ChildComponent a={2} b={3} key="1" />,
<ChildComponent a={1} b={2} key="2" />
];
var children_sorted = children.slice();
children_sorted.sort((a,b) => a.type.calculate(a.props) - b.type.calculate(b.props));
return (
<h1>Children {children} -> {children_sorted}</h1>
);
}
}
ReactDOM.render(
<ParentComponent/>,
document.getElementById('react_example')
);
<div id="react_example"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.2/umd/react-dom.production.min.js"></script>
If the children need to make the same calculation, they can use calculate as well, via ChildComponent.calculate(this.props) (which will always use the version of calculate on ChildComponent or this.constructor.calculate(this.props) which will use the version of calculate on the constructor that created the instance (under normal circumstances). If it's a subclass of ChildComponent and doesn't need its own calculate, it will inherit ChildComponent's (yes, static methods are inherited with class syntax).
Is there a reason for using a component for the calculations?
This can be a simple function. This example could probably be simplified further....
class ParentComponent extends React.Component {
calculate = (a, b) => a + b;
render() {
const children = [this.calculate(2, 3), this.calculate(1, 2)];
const children_sorted = [this.calculate(2, 3), this.calculate(1, 2)].sort();
return (
<h1>
{children.map((x) => x)} => {children_sorted.map((x) => x)}
</h1>
);
}
}
ReactDOM.render(<ParentComponent />, document.getElementById('react_example'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="react_example"></div>
Related
After upgrading to react 16 I am getting null in console.log(this.child)
My parent component
import EditReview from './partials/editReview'
class VenueDetails extends Component {
constructor(props) {
super(props)
this.child = React.createRef();
}
editButtonClick = () => {
console.log(this.child)
this.child.current.onEditClick()
}
render() {
return (
<div>
<button className="pull-right" onClick={() => this.editButtonClick(review, i)}>edit</button>
<div className="place-review-text">
<EditReview {...this.props}/>
</div>
</div>
)
}
}
My child component
class EditReview extends Component {
onEditClick(review, editIndex) {
console.log('ppp')
}
render() {
return ()
}
}
export default EditReview
I need to call onEditClick from the parent component. I tried this but doesn't work.
Kindly help me
You have to assign the ref:
<EditReview {...this.props} ref={this.child} />
Also, you don't need to use inline arrow function:
onClick={() => this.editButtonClick(review, i)}
// ------^^^^^ not required
// because, you're already using public class method
Just use:
onClick={this.editButtonClick(review, i)}
Define your method like this:
editButtonClick = (review, index) => { // to access review, i
Let me start by saying that this example is very simple and can be solved with React.cloneElement. But I want more freedom and the project will be more complex, so I'd like to find a solution.
I would also like to understand what I'm missing :/
I want to be able to augment the children of a Parent component with props and methods (hence the HOC). It would start from here:
<Parent>
<aChild />
<anotherChild />
<yetAnotherChild />
</Parent>
And this is the Parent component (called Sequence in my project), so far:
import React, { Component } from 'react';
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends Component {
constructor(props) {
super(props);
this.state = {
pointer: 0,
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Children = React.Children.map(this.props.children, Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
// do stuff
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
export default Sequence;
I get the following error:
You can play with the code here: https://codesandbox.io/s/6w1n5wor9w
Thank you for any help!
This answer will not solve your problem but maybe gives a hint why this is not possible. At first I was surprised why your code does not work, even though I'm not an experienced React developer it seems ok map this.props.children through with React.Children.map and return the desired Component with your HOC. But it did not work.
I tried to debug it a little bit and did some search. I've learned props.children actually contains the elements itself not the instances of components. Even, React.Children.map does not have any effect on this.
Here is a working snippet proves that your problem is not related with the HOC. I've used an array of components instead of mapping through props.children.
class Line1 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 1</div>;
}
}
class Line2 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 2</div>;
}
}
class Line3 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 3</div>;
}
}
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends React.Component {
constructor(props) {
super(props);
this.state = {
pointer: 0
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Arr = [ Line1, Line2, Line3 ];
this.Children = this.Arr.map(Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
this.next();
}
next() {
// Clearly, render the next element only if there is, a next element
if (this.state.pointer >= this.Arr.length - 1) {
return;
}
this.setState({ pointer: this.state.pointer + 1 });
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
ReactDOM.render(
<Sequence />,
document.getElementById("app")
);
<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="app"></div>
You are returning <Child /> instead of Child in Sequence.js render method. Here is my edited copy - codesandbox
I have embedded a reactjs component into an existing HTML page by referencing it's ID like described in React's tutorial:
ReactDOM.render(
<Page />,
document.getElementById('my-react-compnent')
);
Then:
<div id="my-react-compnent></div>
The component is displayed and works as expected.
Now I want to link a button located on that page to my component (to be specific I would like to retrive its state, but for the example even invoking one of its methods would be fine).
In other words - when clicking the outside button, I want to invoke a method from the Page component?
How can I do that?
The Old Recommendation
Assigning the returned value from ReactDOM.render does allow access to the component and it's methods. For example, in a simple app, we might have:
const PageComponent = ReactDOM.render(<Page />, document.getElementById("app"));
which we can then access using PageComponent, and any of its methods can be accessed with PageComponent.METHOD.
However, according to the docs this might be changed or deprecated and is not recommended.
The New Recommendation
The new recommendation is to attach a callback ref to the root element. Using the same example above:
const PageComponent = ReactDOM.render(<Page ref={(pageComponent) => {window.pageComponent = pageComponent}}/>, document.getElementById("app"));
which we can then access using window.pageComponent, and any of its methods can be accessed with window.pageComponent.METHOD.
This also works for child components.
Here's a full example:
class ChildComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
returnCounter = () => {
return this.state.counter;
}
increment = (event) => {
event.stopPropagation();
this.setState(prevState => {
return {
counter: prevState.counter + 1
}
})
}
render() {
return (
<div onClick={this.increment}>
Child Value - {this.state.counter} - Click to increment
</div>
)
}
}
class Page extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
returnCounter = () => {
return this.state.counter;
}
increment = () => {
this.setState(prevState => {
return {
counter: prevState.counter + 1
}
})
}
render() {
return (
<div onClick={this.increment}>
<div>Parent Value - {this.state.counter} - Click to increment</div>
<ChildComponent ref={(childComponent) => {window.childComponent = childComponent}}/>
</div>
)
}
}
ReactDOM.render(<Page ref={(pageComponent) => {window.pageComponent = pageComponent}} />, document.getElementById("app"));
const parentBtn = document.getElementById("parentButton");
parentBtn.addEventListener("click", event => {
alert(window.pageComponent.returnCounter());
});
const childBtn = document.getElementById("childButton");
childBtn.addEventListener("click", event => {
alert(window.childComponent.returnCounter());
});
<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="app"></div>
<button id="parentButton">Get Parent State</button>
<button id="childButton">Get Child State</button>
I'm trying to get an instance of the class ActionEditor So that I'd be able to use its methods later:
function render() {
const toRender = responseActions.map((actionInstance) => {
currentActionEditing=actionInstance;
return <li>{ actionInstance === expandedAction ? <ActionEditor id={actionInstance.title} action={getActionByKey(actionInstance.actionType)} instance={actionInstance} state={actionInstance} /> : <button onClick={createOnClick(actionInstance)}>{actionInstance.title}</button>}</li>;
});
ReactDOM.render(
<div>
<div>{toRender}</div>
<button style={styleButtonGenerate} onClick={onGenerateClick}>Generate</button>
</div>,
document.getElementById('root')
);
}
I've attempted to use it through an onClick method like so:
function onGenerateClick() {
var editor = document.getElementById(currentActionEditing.title);
editor.prototype = ActionEditor;
editor.methodIWantToUse();
}
But it always turns out to be null/undefined.
I understand that it's not the best example but it should be enough to demonstrate the issue.
Is there a way around this?
I think what you want here is to save a ref to the component so it can be accessed, see in the example below how the sayHi method is called from the parent component.
class MyComponent extends React.Component {
sayHi() {
console.log('hi');
}
render() {
return (<div>I'm a component!</div>)
}
}
class App extends React.Component {
render() {
// just a way to show how to access a child component method.
setTimeout(() => {
this.node.sayHi();
}, 1000)
return (<MyComponent ref={(node) => this.node = node}/>)
}
}
ReactDOM.render(<App />, document.querySelector("body"))
<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>
How to access the refs of children in a parent to do something with them in the parent function?
class Parent extends Component {
someFunction(){
// how to access h1 element of child in here ??
}
render() {
return (
<Child />
);
}
}
class Child extends Component {
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
To add to Shubham's answer, the child refs have to be accessed inside of componentDidMount() inside parent. Something like:
class Parent extends React.Component {
componentDidMount(){
var elem1 = this.refs.child1.refs.childRefName;
}
return (
<View>
<Child1 ref='child1'/>
<Child2 />
<Child3 />
</View>
);
}
You can access the child refs by providing a ref to the child element and accessing it like ReactDOM.findDOMNode(this.refs.child.refs.hello)
In your case the child component doesn't begin with Uppercase letter which you need to change.
class App extends React.Component {
componentDidMount() {
console.log(ReactDOM.findDOMNode(this.refs.child.refs.hello));
}
render() {
return (
<Child ref="child"/>
);
}
}
class Child extends React.Component {
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<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>
<divi id="app"></div>
You can also use React.forwardingRef method to make the Child component able to receive and define a ref from its parent.
Here is the documentation for the method:
https://reactjs.org/docs/forwarding-refs.html
And here is an example of how you might implement it in your code:
const Child = React.forwardRef((_, ref) => (
<h1 ref={ref}>Child Component</h1>
));
function Parent() {
var h1 = React.createRef();
React.useEffect(() => {
console.log(h1.current);
});
return <Child ref={h1} />;
}
https://reactjs.org/docs/forwarding-refs.html
I hope it helps.