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>
Related
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>
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
So, I have a class like this:
class Blah extends Component {
constructor(props) {
super(props);
}
handleComponent = (event) => {
let divid = event.target.getAttribute('id');
if (divid === 'col') {
// I want to render component by this condition
} else if (divid === 'ro') {
// or I want to render component by this condition
} else {
//or I want to render component by this condition
}
};
render() {
const { classes } = this.props;
return (
<div>
<div id = 'col' onClick={this.handleComponent}>Sheep</div>
<div id = 'ro' onClick={this.handleComponent}>Cow</div>
<div id = 'ball' onClick={this.handleComponent}>Dog</div>
{ I want to render my component here after click }
</div>
);
}
}
I have another class written on top of this:
class Flow extends Component {
constructor(props){
super(props);
};
render() {
return(
<div style={{background:'somecolor'...blah blah}}>Clap</div>
);
}
}
And I am Passing this by:
var foo = withStyles(styles)(Flow)
I have tried returning components but I am not getting anywhere.
I can use ternary operator but it still will render only one of two but I have three component have three design for each of them.
I want to render one of them to render on some condition as stated above.
If I use states that for toggle that will too have two components for render. Don't go on the code, this is made up, So any Ideas ? Fragments ? Any help will be appreciated. Thank you.
To render component by condition simply use switch statement.
In my example we use state to store current active component.
renderMyComponent method takes care of rendering one of three possible components.
handleChange method changes current state and triggers new render of App component.
This example use class properties plugin.
renderMyComponent = () => {
means autobind and is the same as using in constuctor method
this.renderMyComponent = this.renderMyComponent.bind(this);
Working example:
const ComponentOne = () => <div>Hi, i am component one</div>;
const ComponentTwo = () => <div>Hi, i am component two</div>;
const ComponentThree = () => <div>Hi, i am component three</div>;
class App extends React.Component {
state = { current: 0 }
renderMyComponent = () => {
// Our switch conditional render
switch(this.state.current) {
case 0:
return <ComponentOne />;
case 1:
return <ComponentTwo />;
case 2:
return <ComponentThree />;
default:
return null;
}
}
handleChange = (event) => {
// We are looking for data-trigger attribute
// In this example we expect type number but trigger holds string
// That's why we 'cast' to a number using Number()
const current = Number(event.target.dataset.trigger);
// Sets new state of current component and triggers new render
this.setState({ current })
}
render() {
return (
<div>
<div>
Pick component to render
<button
type="button"
data-trigger="0"
onClick={this.handleChange}
>
Render 1
</button>
<button
type="button"
data-trigger="1"
onClick={this.handleChange}
>
Render 2
</button>
<button
type="button"
data-trigger="2"
onClick={this.handleChange}
>
Render 3
</button>
</div>
{this.renderMyComponent()}
</div>
)
}
}
ReactDOM.render(<App />, 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>
Reviewing your code:
You don't need constructor here.
...
constructor(props){
super(props);
}
...
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>
What I'm trying to do is call a function defined in a parent element from its child, but bind this to the child calling the function, rather than the parent where the function runs.
Is there a way to do this and would this be an anti-pattern if so? Thank you.
Parent Function to Pass to Child
onSelect = (event => {
// Some code where `this` is the scope of the child calling the function
// Not the scope of the parent where the function is defined
}
Parent Render Function
render() {
return (
<Child onSelect={this.onSelect} />
)
}
Child Render Function
render() {
return (
<button onClick={this.props.onSelect.bind(this)} />
)
}
The problem is you're defining onSelect as an arrow function, so it closes over this rather than using the this it was called with. Just make it a method or non-arrow function:
class Parent extends React.Component {
onSelect() {
console.log(this.constructor.name);
console.log(this.note);
}
render() {
return <Child onSelect={this.onSelect} />;
}
}
class Child extends React.Component {
constructor(...args) {
super(...args);
this.note = "I'm the child";
}
render() {
return (
<button onClick={this.props.onSelect.bind(this)}>Click Me</button>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<div id="root"></div>
<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>
But you might consider binding onSelect once rather than repeatedly (e.g., not in render), perhaps in Child's constructor. But it really only matters if render will get called a lot. E.g.:
class Parent extends React.Component {
onSelect() {
console.log(this.constructor.name);
console.log(this.note);
}
render() {
return <Child onSelect={this.onSelect} />;
}
}
class Child extends React.Component {
constructor(...args) {
super(...args);
this.note = "I'm the child";
this.onSelect = this.props.onSelect.bind(this);
}
render() {
return (
<button onClick={this.onSelect}>Click Me</button>
);
}
}
ReactDOM.render(
<Parent />,
document.getElementById("root")
);
<div id="root"></div>
<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>
Is there a way to do this and would this be an anti-pattern if so?
Pass a function (not an arrow function), and bind it in the constructor.
It's an anti pattern because the parent needs to be aware of the inner working of the child, and this breaks encapsulation.
How do you do that:
Use a standard method, and not an arrow function:
onSelect(e) {
this.setState({
selected: !!e.target.value
});
}
Bind the method in the constructor:
constructor(props) {
super(props);
this.state = {
selected: false
};
this.onSelect = this.props.onSelect.bind(this);
}
Working example:
const { Component } = React;
class Child extends Component {
constructor(props) {
super(props);
this.state = {
selected: false
};
this.onSelect = this.props.onSelect.bind(this);
}
render() {
const {selected} = this.state;
return (
<div>
<input onSelect={this.onSelect} defaultValue="I'm the text" />
<div>{selected ? 'selected' : 'not selected'}</div>
</div>
);
}
}
class Parent extends Component {
onSelect(e) {
this.setState({
selected: !!e.target.value
});
}
render() {
return (
<Child onSelect={this.onSelect} />
)
}
}
ReactDOM.render(
<Parent />,
demo
);
<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="demo"></div>