call child function from parent in react 16 - javascript

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

Related

Troubles with state

I'm just started to learn react, and i have a question
Well, i can impact on state from one component to another. But can i do it in reverse?
Here's what i mean:
import React from 'react';
import Butt from './Button';
class Checkbox extends React.Component {
constructor(props) {
super();
}
render() {
return (
<div>
<Butt arg={13} />
</div>
);
}
}
export default Checkbox;
import React from 'react';
class Butt extends React.Component {
constructor(props) {
super();
this.state = {
s1: props.arg,
};
}
add = () => {
let val = this.state.s1;
val++;
this.setState({ s1: val });
};
render() {
return (
<div>
<label>
<label>
<button onClick={this.add}>add</button>
<div>{this.state.s1}</div>
</label>
</label>
</div>
);
}
}
export default Butt;
Sorry for my silly question. Thanks in advance :)
I am not sure about your question, but in react, there is a one-way flow (from parent to child) for transferring information (props, states, or ...). If you want to have access to states everywhere or set them in each direction you should use Redux or context or any other state management.
You're updating the Butt state from inside Butt so this will work fine. It won't change the value of this.props.arg though, if that's what you're asking.
Props are always non-mutable.
What you can do is have two components share the state of their parent...
class Parent extends React.Component {
state = {
val = 0
}
render () {
return (
<>
<Child1
val={this.state.val}
onChange={newVal => this.setState({ val: newVal })}
/>
<Child2
val={this.state.val}
onChange={newVal => this.setState({ val: newVal })}
/>
</>
)
}
}
Then inside the child components pass the updated value to onChange...
class Child1 extends React.Component {
handleChange() {
this.props.onChange(this.props.val + 1)
}
render() {
return (
<Button onClick={() => this.handleChange()}>
Update value
</Button>
)
}
}
This way you're just passing a new value from Child to Parent and letting Parent decide what to do with it.
Whether Child1 or Child2 sends the new value, both children will get updated when Parent calls this.setState({ val: newVal }) and changes this.state.val.

unable to find a React.Component by id

I have a React.Component with render() declared this way:
render(){
return <div>
<button id="butt" onClick={()=> $("#noti").change("test") }>click me</button>
<Notification id="noti" onMounted={() => console.log("test")}/>
</div>
}
And this is my Notification class:
class Notification extends React.Component {
constructor(props) {
super(props)
this.state = {
message: "place holder",
visible: false
}
}
show(message, duration){
console.log("show")
this.setState({visible: true, message})
setTimeout(() => {
this.setState({visible: false})
}, duration)
}
change(message){
this.setState({message})
}
render() {
const {visible, message} = this.state
return <div>
{visible ? message : ""}
</div>
}
}
As the class name suggests, I am trying to create a simple notification with message. And I want to simply display the notification by calling noti.show(message, duration).
However, when I try to find noti by doing window.noti, $("#noti") and document.findElementById("noti"), they all give me undefined, while noti is displayed properly. And I can find the butt using the code to find noti.
How should I find the noti? I am new to front end so please be a little bit more specific on explaining.
It's not a good idea using JQuery library with Reactjs. instead you can find a appropriate react library for notification or anything else.
Also In React we use ref to to access DOM nodes.
Something like this:
constructor(props) {
super(props);
this.noti = React.createRef();
}
...
<Notification ref={this.noti} onMounted={() => console.log("test")}/>
more info: https://reactjs.org/docs/refs-and-the-dom.html
I have hardcoded the id to 'noti' in the render method. You can also use the prop id in the Notification component.I have remodelled the component so that you can achieve the intended functionality through React way.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messageContent: 'placeholder'
}
}
setMessage = (data) => {
this.setState({messageContent : data});
}
render() {
return (
<div className="App">
<button id='butt' onClick= {() => this.setMessage('test')} />
<Notification message = {this.state.messageContent} />
</div>
);
}
}
class Notification extends React.Component {
render () {
const {message} = this.props;
return (
<div id='noti'>
{message}
</div>
)
}
}
Before beginning: Using id/class to reach DOM nodes is not suggested in React.js, you need to use Ref's. Read more at here.
In your first render method, you give id property to Notification component.
In react.js,
if you pass a property to some component, it becomes a props of that
component. (read more here)
After you give the id to Notification, you need to take and use that specific props in your Notification component.
You see that you inserted a code line super(props) in constructor of Notification? That means, take all the props from super (upper) class and inherit them in this class.
Since id is HTML tag, you can use it like:
class Notification extends React.Component {
constructor(props) {
// inherit all props from upper class
super(props);
this.state = {
message: "place holder",
visible: false,
// you can reach all props with using this.props
// we took id props and assign it to some property in component state
id: this.props.id
}
}
show(message, duration){
// code..
}
change(message){
// code..
}
render() {
const {visible, message, id} = this.state
// give that id to div tag
return <div id={id}>
{message}
</div>
}
}
You can't pass id/class to a React Component as you would declare them in your normal HTML. any property when passed to a React Component becomes a props of that component which you have to use in the component class/function.
render() {
const {visible, message} = this.state
// give your id props to div tag as id attr
return <div id={this.props.id}>
{message}
</div>
}
This answer does not provide the exact answer about selecting a component as you want. I'm providing this answer so you can see other alternatives (more React way maybe) and improve it according to your needs.
class App extends React.Component {
state = {
isNotiVisible: false
};
handleClick = () => this.setState({ isNotiVisible: true });
render() {
return (
<div>
<button onClick={this.handleClick}>Show Noti</button>
{this.state.isNotiVisible && (
<Noti duration={2000} message="This is a simple notification." />
)}
</div>
);
}
}
class Noti extends React.Component {
state = {
visible: true
};
componentDidMount() {
setTimeout(() => this.setState({ visible: false }), this.props.duration);
}
render() {
return this.state.visible && <div>{this.props.message}</div>;
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root" />

Should functions that call a callback function be bound?

If I pass a callback function from Parent to GrandChild, should handleClick be bound in Child and GrandChild?
Parent.js
class Parent extends React {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log('Clicked!');
}
render() {
return (
<Child onClick={this.handleClick} />
);
}
}
Child.js
class Child extends React {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { onClick: callback } = this.props;
callback();
}
render() {
return (
<GrandChild onClick={this.handleClick} />
);
}
}
GrandChild.js
class GrandChild extends React {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
const { onClick: callback } = this.props;
callback();
}
render() {
return (
<div onClick={this.handleClick} />
);
}
}
Functions can be accessed via props without being bound when passed down to children. It's only necessary to bind to this inside the component where the function is originally defined.
You only need to do onClick={this.props.handeClick}
or if you want to pass some data, you can do it like this:
onClick={(someData) => this.props.handeClick(someData)}
EDIT: Just to clarify, you only need to bind handleClick in Parent.js. You can then just pass this function down via props and access it in the child components using this.props.
The answer is that the context this should always be the one where the logic is, so if the logic that handles the handleClick is in the class Parent so, the context is.
Other than that there are some problems in your code.
1.Your component classes must extend React.Component or React.PureComponent and not React itself (maybe it's a copy-paste error, but if not fix it).
See: https://reactjs.org/docs/components-and-props.html#function-and-class-components
2.You don't have to name every single props that should be passed through all child components, you can use the spread syntax if you code using ES6.
See: https://reactjs.org/docs/jsx-in-depth.html#spread-attributes
class Child extends React.Component {
render() {
return (
// this is passing all props of Child to GrandChild
<GrandChild {...this.props} />
);
}
}
3.For components that don't have state, use function instead of class, it's more performant and also the code is smaller.
function Child(props) {
return (
<GrandChild {...props} />
);
}
Finally your code could look like this:
function Parent(props) {
function handleClick() {
console.log('clicked');
}
return <Child onClick={handleClick} />;
}
function Child(props) {
return <GrandChild {...props} />;
}
function GrandChild(props) {
return <div onClick={props.onClick} />;
}
Arrow function is better. And context this will be automatically bind.
handleClick = () => {}
Inline function is bad (unnecessary render possible). It is better like this:
handleClick = (someData) => this.props.handeClick(someData)
And
onClick={this.handleClick}

How can I wrap the Children of a Parent component in a HOC and render them in React?

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

How can I use 'this' in defaultProps React.Component class syntax?

In the past I was able to set a default prop that used this like so...
let MyButton = React.createClass({
getDefaultProps() {
return {
buttonRef: (ref) => this.button = ref
};
},
But now that I'm using JS classes MyButton looks like this...
class MyButton extends React.Component {
static defaultProps = {
buttonRef: (ref) => this.button = ref
};
And I get an error saying that this is undefined.
What do I need to do to be able to set default props that use this?
EDIT: Add some context
Setting a default buttonRef prop allowed me to use the ref in the MyButton component but also always be able to pass in a custom ref if a parent component needs to access the MyButton DOM node.
class MyButton extends React.Component {
static defaultProps = {
buttonRef: (ref) => this.button = ref
};
componentDidMount() {
Ladda.bind(this.button);
}
render() {
return (
<button
ref={(ref) => this.button = this.props.buttonRef(ref)}
onClick={this.props.handleClick}
>
{this.props.buttonText}
</button>
);
}
}
So then my button can always get hooked in to Ladda: Ladda.bind(this.button)
And if I need to access that button's DOM node in a parent component I can do so by passing in buttonRef as a prop like...
class MouseOverButton extends React.Component {
componentDidMount() {
this.mouseEnterButton.addEventListener("mouseover", doSomething(event));
}
render() {
return (
<div>
<MyButton
buttonRef={(ref) => this.mouseEnterButton = ref}
/>
</div>
);
}
}
EDIT: Apparently my simplified example doesn't illustrate the point well enough so I can come up with a more practical example or y'all can just answer the original question: What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
Where this convention of having a defaultProp for a specific element's ref has been useful is when using a HOC that hooks a nested component into some 3rd party API. I have an AddressSearch HOC that takes a node via a function passed to the wrapped component. Then it uses that node to hook it up with Google's Places API.
So I've got my addAddressSearch(component) function from my HOC. It adds the functions needed to hook up the Google places API. But for Google's API to work I need to know what DOM node I'm working with. So I pass my Input component an inputRef that gives my AddressSearchInput access to the appropriate node.
class AddressSearchInput extends React.Component {
static defaultProps = {
inputRef: (ref) => this.addressSearchInput = ref
};
componentDidMount() {
let node = this.addressSearchInput;
this.props.mountAddressSearch(node);
}
render() {
return (
<div>
<Input
inputAttributes={inputAttributes}
inputRef={(ref) => this.addressSearchInput = this.props.inputRef(ref)}
labelText={<span>Event address or venue name</span>}
labelClassName={labelClassName}
/>
</div>
);
}
}
AddressSearchInput = addAddressSearch(AddressSearchInput);
module.exports = AddressSearchInput;
// Here's the Input component if that helps complete the picture here
class Input extends React.Component {
render() {
return (
<div>
<Label>{this.props.labelText}</Label>
<HelperText text={this.props.inputHelperText} />
<input
{...this.props.inputAttributes}
ref={this.props.inputRef}
></input>
</div>
);
}
}
So now when I want to use my AddressSearchInput in a parent component that needs to add an eventListener to the relevant node I can just pass AddressSearchInput an inputRef prop.
class VenueStreetAddress extends React.Component {
componentDidMount() {
let node = this.venueStreetAddressInput;
this.props.mountValidateOnBlur(node, venueValidationsArray);
},
render() {
return (
<div>
<AddressSearchInput
inputRef={(ref) => this.venueStreetAddressInput = ref}
hasError={this.props.hasError}
/>
{this.props.errorMessageComponent}
</div>
);
}
}
And I can use AddressSearchInput all over the place and it doesn't break anything.
class UserStreetAddress extends React.Component {
componentDidMount() {
let node = this.userStreetAddressInput;
this.props.mountValidateOnBlur(node, userValidationsArray);
},
render() {
return (
<div>
<AddressSearchInput
inputRef={(ref) => this.userStreetAddressInput = ref}
hasError={this.props.hasError}
/>
{this.props.errorMessageComponent}
</div>
);
}
}
Maybe this way is convoluted and wrong but I don't have the time to figure out another way to do it on my own. So either point me to a tutorial(s) on the best way to hook into 3rd party APIs and add dynamic form validation without using refs or answer my original question which is...
What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
EDIT: In attempting to explain my use case I had the idea to make my defaultProps look like this...
static defaultProps = {
inputRef: (ref) => ref
};
which seems to be working without error.
In any case, the original question still stands. What do I need to do to be able to use this in my defaultProps? Can I no longer do that using JS class syntax?
This really should be a method, not a property.
class MyButton extends React.Component {
setButtonRef (ref) {
this.button = ref;
}
componentDidMount() {
Ladda.bind(this.button);
}
render() {
return (
<button
ref={ ref => this.setButtonRef(ref) }
onClick={this.props.handleClick}
>
{this.props.buttonText}
</button>
);
}
}
If you want a ref to the button, bind a variable at the class level and assign it to that class variable. Example:
export default class MyComponent extends Component {
componentDidMount() {
// button will be available as `this.button`
}
button = null; // initialize to null
render() {
return (
<div>
<Button ref={e => { this.button = e; }} />
</div>
);
}
}
Since a static property has no knowledge of class instances, if it's strictly necessary to make a static method aware of a given instance, the only way would be to pass to the static method the instance as an argument:
<button
ref={(ref) => this.button = this.props.buttonRef(this, ref)}
onClick={this.props.handleClick}
>
And in your defaultProp, use the instance:
static defaultProps = {
buttonRef: (instance, ref) => instance.button = ref
};

Categories

Resources