How to initiate function passed from parent via props from grandparent - reactjs

I've got a nest of react components (its a modal).
<Modal> // Grandparent
<Footer> // Parent
<Button label="Click Me" clickAction={
() => {
/* So what goes here? */
}
}/> //Child
</Footer>
</Modal>
Modal passes all children the close modal function. This function gets passed again to all children of footer.
export default class Footer extends React.Component {
constructor(props) {
super(props);
this.handleClose = this.handleClose.bind(this);
}
handleClose() {
this.props.closeModal();
}
render() {
const childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
closeModal: this.handleClose
})
);
return (
<footer}>
{childrenWithProps}
</footer>
);
}
}
The internals of the button class look like this:
constructor(props) {
super(props);
this.click = this.click.bind(this);
}
click() {
this.props.clickAction();
}
render() {
return <a
href='#'
onClick={this.click}
>
{this.props.label}
</a>
}
What I don't want to do is bake the close method right into the button since it's supposed to only have access to it when it's a child of modal footer.
So the thing I'm trying to solve (see the first code block) is what goes in the /* So what goes here? */ area. Using 'this' just returns 'undefined' so I'm having trouble reaching my closeModal method.
Any suggestions?

Ah figured out the problem. The arrow functions didn't have 'this' bound.
<Button label="Click Me" clickAction={
// Nope
// () => {this.closeModal()}
// Yup
function(){this.closeModal()}
}/>

You might consider only passing props to components that need to use them. In other words, just explicitly pass the closeModal function to your button as a prop:
function Footer (props) {
return (
<footer>{props.children}</footer>
)
}
function Button (props) {
return (
<a href='#' onClick={props.onClick}>{props.label}</a>
)
}
class Modal extends React.Component {
constructor (props) {
super(props)
this.closeModal = this.closeModal.bind(this)
}
closeModal () {
/* implement me */
}
render () {
return (
<div>
<Footer>
<Button label='Click Me' onClick={this.closeModal} />
</Footer>
</div>
)
}
This way, you don't have to think about the context of the button, i.e. which props are being "invisibly" passed to it via a parent pattern like the cloneElement technique you've used. Some components which are children of Footer might not need access to closeModal, so there's no reason to pass it to them.

Related

React: how to propagate state to enclosing parent

I have 2 classes to provide the modal-dialog functionality:
import React from 'react'
import Modal from 'react-modal'
export default class ModalBase extends React.Component {
state = { show:false }
handleOpen = opts => {
this.setState( { ...opts, show:true } )
console.info( 'ModalBase handleOpen', this.constructor.name, 'show', this.state.show )
}
handleClose = () => this.setState( { show:false } )
render() {
console.info( 'ModalBase render show', this.state.show )
return <Modal isOpen={this.state.show} onRequestClose={this.handleClose} className="Modal" overlayClassName="Overlay">
{this.props.children}
</Modal>
}
}
and
export default class InfoPopup extends ModalBase {
state = { ...this.state, tech:{} }
render() {
console.info('InfoPopup render show', this.state.show)
return (
<ModalBase>
<div/><div/>
</ModalBase>
)
}
}
When I call InfoPopup.handleOpen({a:42}), the following shows up in the console:
ModalBase handleOpen InfoPopup show true
InfoPopup render show true
ModalBase render show false
so, the ModalBase's state.show is not changed and hence the popup is not shown.
How shall I properly propagate the state to enclosing parent object?
TIA
Use composition instead of inheritance
From the React docs:
React has a powerful composition model, and we recommend using composition instead of inheritance to reuse code between components.
See: https://reactjs.org/docs/composition-vs-inheritance.html
So export default class InfoPopup extends ModalBase is not advised.
1. Let InfoPopup render ModalBase but keep track of open/close state
You could turn it around and have a generic BaseModal component for modal styling that you pass props such as title and content. The InfoPopup keeps track of the opened/closed state. From the same React docs page:
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
2. Render ModalBase and pass the type of modal as prop
You could also always render ModalBase for an info, warning, error modal etc. Then you pass the type of modal as prop to ModalBase. ModalBase determines some specifics based on that type prop.
3. use a render prop
Described here: https://reactjs.org/docs/render-props.html
Let ModalBase accept a function as children prop.
So in InfoPopup:
<ModalBase>
{({ toggle }) => (
<button onClick={toggle} />
)}
</ModalBase>
And in ModalBase:
render() {
return <Modal ...>{this.props.children({ toggle: this.openOrClose })}</Modal>
}
4. Pass a component to ModalBase to render when open
A bit of a variant on 2. You could also pass a component as prop to ModalBase that it should show when it's open.
<ModalBase
modalContent={<InfoPopup />}
/>

Cannot pass a simplified function to onClick event handler ReactJs

In my React app, I have two Components, Main and Menu Component. Main Component is the parent component of Menu. Menu shows a list of items and upon clicking one of the item, it updates Main's state with the help of a function that I pass as props to Menu. Below is the code for better understanding:
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
dishes: dishes,
selectedDish: null
};
this.selectDish=this.selectDish.bind(this);
}
selectDish(dishId){
this.setState({ selectedDish: dishId});
}
render() {
return (
<div className="App">
<Menu dishes={this.state.dishes} onClick={this.selectDish} />
</div>
);
}
}
export default Main;
And below is the Menu Component:
class Menu extends Component {
constructor(props){
super(props);
this.selectdish=this.selectdish.bind(this);
}
selectdish(dishId){
return this.props.onClick(dishId);
}
render() {
const menu = this.props.dishes.map((dish) => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
onClick={this.selectdish(dish.id)}>
</Card>
</div>
);
});
}
}
export default Menu;
I have omissed some irrelevant parts of the code.
So the workflow should be that when we click over one of dishes rendered by the Menu, that dish's id should be passed back to Main and update the state variable ```selectedDish````, as seen in the method selectDish.
But in the browser console, I get the error Cannot update during existing state transition.
The weird thing is that if I don't pass any dish id and set the selectedDish to a fixed value like 1, everything works fine.
Please help me guys to identify if there is any problem in my event handler, because that is the only part that seems to contain an error.
Thank You!
You are not passing the onClick of the Card a function but you are already calling that function with this.selectdish(dish.id). This will initiate the flow on render, not on click.
You have three options here.
You either wrap that in an additional function like this: onClick={() => {this.selectdish(dish.id)}}>. That way, you are passing a function to the onClick, which is required, and not executing that.
Or you make selectdish return a function like this:
selectdish(dishId){
return () => {this.props.onClick(dishId)};
}
Or you add a name to the card and access the name of the element from the click event:
class Menu extends Component {
constructor(props){
super(props);
this.selectdish=this.selectdish.bind(this);
}
selectdish(event){
return this.props.onClick(event.target.name);
}
render() {
const menu = this.props.dishes.map((dish) => {
return (
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
name={dish.id}
onClick={this.selectdish}> // this is now the function without calling it
</Card>
</div>
);
});
}
}
export default Menu;
You have some problem in your code:
property key must be define in root <div>
render method in Menu component returns nothing
-
class Menu extends React.Component {
constructor(props) {
super(props);
this.selectdish = this.selectdish.bind(this);
}
selectdish(dishId) {
return this.props.onClick(dishId);
}
render() {
return this.props.dishes.map(dish => {
return (
<div className="col-12 col-md-5 m-1" key={dish.id}>
<div onClick={() => this.selectdish(dish.id)}>{dish.id}</div>
</div>
);
});
}
}
See my playground where I fixed this issues:
https://codesandbox.io/s/react-playground-ib6pr?file=/index.js

In React, can I create a Component that also acts as a Forwarded Ref object?

I have a need to use forwarded refs
const InfoBox = React.forwardRef((props, ref) => (
<div ref={ref} >
<Rings >
</Rings>
<Tagline />
</div>
));
I also happen already have the code written like this
class InfoBox extends React.Component {
constructor(props) {
super(props)
}
render () {
return (
<div >
<Rings />
<Tagline />
</div>
)
}
basically my InfoBox needs to be a Component because it holds some state, but I also want it to behave like an object that can receive refs from the parent and forward them down to the children (basically React.forwardRef)
After familiarizing myself with React.forwardRef, I can't figure out how to get it to work with my existing React components, which already have functionality attached to state.
do I need to separate the two objects, and wrap one within the other or is there a way I can achieve this in the same object?
the code that wraps Infobox looks like
class AppContainer extends React.Component {
constructor(props) {
super()
this.infobox_ref = React.createRef()
}
componentDidMount() {
// this.infobox_ref.current.innerHTML should return the inner HTML of the infobox
}
render() {
return (
<InfoBox ref={this.infobox_ref}>
)
}
am I using forwarded refs correctly?
In React, the ref prop is not forwarded by default. In order to get a reference in a child component, you have 2 options:
Using a function component wrapped in the forwardRef function. You have already done this:
const InfoBox = React.forwardRef((props, ref) => (
<div ref={ref} >
<Rings >
</Rings>
<Tagline />
</div>
));
Changing the name of the ref prop.
// Parent Component
class AppContainer extends React.Component {
constructor(props) {
super()
this.infobox_ref = React.createRef()
}
componentDidMount() {
// this.infobox_ref.current.innerHTML should return the inner HTML of the infobox
}
render() {
return (
<InfoBox infoboxRef={this.infobox_ref}>
)
}
}
// Child Component
class InfoBox extends React.Component {
render () {
return (
<div ref={this.props.infoboxRef}>
<Rings />
<Tagline />
</div>
)
}
}
Of course, you can also combine them, allowing you to still pass to the ref prop from the parent, but consuming the "fixed" prop in the child class component, as shown here by #tubu13:
class InfoBox extend React.Component{
render() {
return (
<div ref={this.props.infoboxRef}>
<Rings />
<Tagline />
</div>
)
}
}
export default React.forwardRef((props, ref) => <InfoBox {...props} infoboxRef={ref} />)

onclick event for Imported component in react?

I have imported a component from a different file and I want to reset my timer if I click on the imported component's elements. Is there a way to tackle this issue or should I write both components in single jsx ?
import {SampleComponent} from "../SampleComponent";
<div>
<SampleComponent onClick = {?????????}/>
</div>
What you can do here is,
import {SampleComponent} from "../SampleComponent";
<div onClick={??????}>
<SampleComponent/>
</div>
Or you can pass the function from your parent component and add click event on top node of the child component.
<div>
<SampleComponent onHandleClick={() => ??????}/>
</div>
If you want to call a function in parent component, whenever an event (such as in your case an onClick event) occurs in a child component, you will need to pass the parent function as a props.
Here's what it will look like:
class ParentComponent extends React.Component {
handleClick = () => { ... }
render {
return (
<SampleComponent onClick={this.handleClick} />
)
}
}
And here is how your SampleComponent will be:
class SampleComponent extends React.Component {
render {
return (
<div onClick={this.props.onClick}> //to call the function on whole component
<button onClick={this.props.onClick}>Click Me</button> //to call the function on a specific element
</div>
)
}
}
What I have understand so far from your question is that you want to call a function in SampleComponent whenever a click event occurs on it (on SampleComponent).
To do this, here is how your SampleComponent will look like :
class SampleComponent extends React.Component {
.
.
render() {
handleClick = () => { ... }
return (
<div onClick={this.handleClick}>
...
</div>
)
}
Note: For this you don't need to add onClick in parent.
resetTimerHandler = (timer)=>{
timer = 0; or this.setState({timer: 0}) // how ever you are defining timer
}
render(){
let displayTimer;
this.updateTimer(displayTimer)// However you are updating timer
return(
<SampleComponent onClick={this.resetTimerHandler.bind(this,displayTimer)} />)
Without knowing how you are updating your timer I can't really give a specific answer but you should be able to apply this dummy code.
It's hard to answer your question specifically without more context (like what timer are you wanting to reset). But the answer is no, you do not need to implement both components in the same file. This is fundamental to react to pass props like what you tried to do in your question.
Here is an example.
Say your SampleComponent looks like the following:
// SampleComponent.jsx
function SampleComponent({ onClick }) { // the argument is an object full of the props being passed in
return (
<button onClick={(event) => onClick(event)}>Click Me!</button>
);
}
and the component that is using SampleComponent looks like this:
import SampleComponent from '../SampleComponent';
function resetTimer() {
// Add logic to reset timer here
}
function TimerHandler() {
return (
<SampleComponent onClick={() => resetTimer()} />
);
}
Now when you click the button rendered by SampleComponent, the onClick handler passed from TimerHandler will be called.
A prop on a React component is really just an argument passed into a function :)

Reactjs focus an input field located in a child component which is inside another child component

I am currently trying to focus an input field that is located in a child component which is inside another child component. consider the following,
Parent.js
child1.js
child2.js
I would like to focus input field in child2 when a button is clicked from parent.js.
I don't say using this technique it's good but you can achieve this by creating a setRef function who get pass by the child 1 to the child 2. Make sure to read this https://reactjs.org/docs/refs-and-the-dom.html why refs is not the best thing. For me I would use props callback.
But if you really want to use ref this is how you can do. I put the code example here. You can also play with the code here https://codesandbox.io/s/5x8530j3xn
class Child2 extends React.Component {
render() {
return (
<div>
<input ref={this.props.setRef} />
</div>
);
}
}
class Child1 extends React.Component {
render() {
return (
<div>
<Child2 setRef={this.props.setRef} />
</div>
);
}
}
class Parent extends React.Component {
setRef = ref => {
this.input = ref;
};
focus = () => {
this.input.focus();
};
render() {
return (
<div>
<Child1 setRef={this.setRef} />
<button onClick={this.focus}>Go Focus</button>
</div>
);
}
}
Just use plain vanilla JavaScript inside a React method.
handleFocus () {
document.getElementById('inputField').focus()
}
<button onClick={this.handleFocus}>My Button</Button>
This will focus the input after clicking the button.

Resources