I am new to React and trying to learn the framework from the ground up. I have built a few simple components(for a typical restaurant website), but I am facing problems understanding event handling. Basically, I have an app component which just calls the Main component, which in turn calls a Menu and Dishdetail component. So the hierarchy is App --> Main --> (Menu and Dishdetail).
App (Just call Main)
return (
<div>
**<Main/>**
</div>
);
Main (Calling Menu component with props) Here I use the onClick event.
**<Menu dishes={this.state.dishes}
onClick={(dishId) => this.onDishSelect(dishId)} />**
Menu (Using the onClick event to render something using the RenderMenuItem functional component)
const menu = props.dishes.map((dish) => {
return (
<div key={dish.id} className="col-12 col-md-5 mt-5 m-1">
**<RenderMenuItem dish={dish} onClick={props.onClick} />**
</div>
)
});
The RenderMenuItem functional component:
function RenderMenuItem({ dish, **onClick** }) {
return (
**<Card onClick={() => onClick(dish.id)}>**
<CardImg width='100%' src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle>{dish.name}</CardTitle>
</CardImgOverlay>
</Card>
)}
Everything is working fine but I am having problems understanding the event handler and I'm also new to arrow functions. According to my understanding:
App calls the Main component which in turn calls the menu component with the 2 props. Here I use the arrow function as a response to onClick event to set the state of the component. ( So I know which dish is selected). But I am also passing it as a prop? Or am I not?
Once the execution flows into the Menu component it calls the RenderMenuItem with the dish selected in the map 'loop' and the same prop onClick it received. What is going on here? Is it just instructing the program to call the function in the Main component (just change state again as in point 1)?
In the RenderMenuItem component I have no idea what is going on with the onClick attribute aside from the fact that it is calling a function called onClick with parameter dish.id.
Can someone explain in detail what exactly happens when you pass an event attribute like onClick to child components?
In your code, onClick is both event handler and prop. Do not use onClick as a prop.
Yes the onClick() is also passed down to child components like any other prop.
You have again passed the onClick you received in the Menu component as a prop on to the RenderMenuItem component. When Menu component is clicked, props.onClick function will be called with click event. Check the following example code
function Welcome(props) {
return <h1 onClick={props.onClick}>Hello, {props.name}</h1>;
}
const element = <Welcome name="Sara" onClick={(id)=>{ console.log(id) }}/>;
ReactDOM.render(element, document.getElementById('root'));
But you cannot pass an actual prop to that function in this state, for that you have to wrap it within an arrow function, which is exactly what you have done in RenderMenuItem component.
function Welcome(props) {
return <h1 onClick={() => {props.onClick(12)}>Hello, {props.name}</h1>;
}
If your intention is just to pass the function to child component, use a different prop name other than onClick.
const menu = props.dishes.map((dish) => {
return (
<div key={dish.id} className="col-12 col-md-5 mt-5 m-1">
**<RenderMenuItem dish={dish} clickHandler={props.onClick} />**
</div>
)
});
P.s. Do not use arrow functions in render, it will create new function on each render. Bind a function with the class controller and use it in render.
class Example extends Component {
constructor(props, context) {
super(props, context);
this.clickHandler = this.clickHandler.bind(this);
}
clickHandler(id) { // Use curried function if you need event clickHandler = (id) => (event) => {}
// do something
}
render() {
return (
<div onClick={this.clickHandler(2)}></div>
);
}
}
Related
I am constructing some node objects in a function(prepareNodes) to pass to React Flow within a functional component A (lets say), and I have defined a custom node component(CardNode) stateless, which has a button. On button click it should trigger the function(prepareNodes) defined within Component A.
function ComponentA = ({ selectedNodes }) => {
const reactFlowWrapper = useRef(null);
const [elements, setElements] = useState([]);
const [edges, setEdges] = useState([]);
const prepareNode = async (nodeid) => {
//some service calls to fetch data and constuct nodes
setElements([ ...nodes]);
setEdges([...edges]);
}
return (
<ReactFlowProvider>
<div className="reactflow-wrapper" ref={reactFlowWrapper}>
<ReactFlow
nodes={elements}
edges={edges}
//some properties
>
</ReactFlow>
</div>
</ReactFlowProvider>
)
};
export default ComponentA;
function CardNode({ data }) {
const renderSubFlowNodes = (id) => {
console.log(id);
//prepareNode(id)
}
return (
<>
<Handle type="target" position={Position.Top} />
<div className="flex node-wrapper">
<button className="btn-transparent btn-toggle-node" href="#" onClick={() => renderSubFlowNodes(data['id']) }>
<div>
<img src={Icon}/>
</div>
</button>
</div>
<Handle type="source" position={Position.Bottom}/>
</>
);
}
export default CardNode;
I looked for some references online, and most of them suggest to move this resuable function out of the component, but since this function carries a state that it directly sets to the ReactFlow using useState hook, I dont think it would be much of a help.
Other references talks about using useCallback or useRefs and forwardRef, useImperativeHandle especially for functional component, Which I did not quite understand well.
Can someone suggest me a solution or a work around for this specific use-case of mine.
You can add an onClick handler to the each node, and within the node view you call this handler on click.
In the parent Component within the onClick handler you can call prepareNode as needed.
useEffect(() => {
setElements(
elements.map(item => {
...item,
onClick: (i) => {
console.log(i);
prepareNode();
},
})
)},
[]);
The classical approach is to have a parent object that defines prepareNode (along with the state items it uses) and pass the required pieces as props into the components that use them.
That "parent object" could be a common-ancestor component, or a Context (if the chain from the parent to the children makes it cumbersome to pass the props all the way down it).
So what I have is two components - one is MainComponent and the other is MenuComponent written in ReactJS
The MenuComponent is imported into the MainComponent
The MainComponent is as shown below:
import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import Menu from './MenuComponent';
import DishDetail from './DishdetailComponent';
import { DISHES } from '../shared/dishes';
class Main extends Component{
constructor(props){
super(props);
this.state={
dishes: DISHES,
selectedDish: null
}
}
onDishSelect(dishId){
this.setState({selectedDish: dishId})
}
render(){
return(
<div className="App">
<Navbar dark color="primary">
<div className="container">
<NavbarBrand href="/">Ristorante Con Fusion</NavbarBrand>
</div>
</Navbar>
<Menu dishes={this.state.dishes} onClick={(dishId)=>this.onDishSelect(dishId)}/>
<DishDetail dish={this.state.dishes.filter((dish)=>dish.id===this.state.selectedDish)[0]}/>
</div>
)
}
}
export default Main;
The render method of MenuComponent is as shown below:
render(){
const menu=this.props.dishes.map((dish) => {
return(
<div className="col-12 col-md-5 m-1">
<Card key={dish.id}
onClick={() => this.props.onClick(dish.id)}>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle><h5 className='font'>{dish.name}</h5></CardTitle>
</CardImgOverlay>
</Card>
</div>
);
});
I'm totally new to React, so I would like to know how the onClick event handler defined in MenuComponent works:
So when a card is clicked (in MenuComponent), is the this.props.onClick invoked? Why
is this.props used here?
The same onClick method is used in the MainComponent, with a dishId
(from where?) passed as parameter, I'm kind of confused on how this
all onClick event is processed
You're using this.props to update the state in the parent component. Without using this.props, there is no way to update state in the parent component with your child component. This is how you update state with a child component.
When you import a component into another component, that imported component becomes that components child.
Yes when a card is clicked, it invokes the onClick prop you have created.
You are right.
The dish.id is passed to props.onClick when click occurs on a card. and
props.onClick is onDishSelect actually, when called with an argument, it will set parent component's state.selectedDish equal to that argument(in this case child's dish id)
Here You are passing onClick method as props to Menu component with argument
<Menu dishes={this.state.dishes} onClick={(dishId)=>this.onDishSelect(dishId)}/>
Here you are calling that onClick props inside Menu component when card is being clicked passing dishId as argument
<Card key={dish.id}
onClick={() => this.props.onClick(dish.id)}>
<CardImg width="100%" src={dish.image} alt={dish.name} />
<CardImgOverlay>
<CardTitle><h5 className='font'>{dish.name}</h5></CardTitle>
</CardImgOverlay>
</Card>
When you are clicking on the <Card> element, this fires the onClick function that is received by MenuComponent via props. So basically this.props is an object representing props passed to the component and from that object you're extracting your onClick which in case of implementation of this component in MainComponent is your onDishSelect method.
You could as well invoke the MenuComponent in different context, and could pass different function as the onClick prop.
const SomeOtherComponent = () => {
const logHelloOnClick = () => {
console.log('hello');
}
return (
<MenuComponent onClick={logHelloOnClick}/>
)
}
In this case when you clicked on the Card, "hello" msg would be logged to the console.
Therefore try to realize that props passed to a component are basically the same as arguments passed to any other function.
const logTestToConsole = text => {
console.log(text)
};
const functionMimickingComponent = props => {
const taskToRun = props.tastToRun;
taskToRun('hello');
};
functionMimickingComponent({ taskToRun: logTestToConsole }); // this logs "hello" to the console
in my react's App.js's return i am currently calling this.searchVenues() in a way that works but is messy and i know there is a better way. The searchVenues() function lives in App.js and I have buttons that need to be in their own component, then just <ButtonComponent/> instead of:
render() {
return (
<div className="App container-fluid">
<Navbar/>
<div className="row">
<div className="col-xs-3">
<button onClick ={() => this.searchVenues("yoga+coffee", "5")}>5</button>
<button onClick ={() => this.searchVenues("yoga+coffee", "10")}>10</button>
<button onClick ={() => this.searchVenues("yoga+coffee", "15")}>15</button>
<SideBar {...this.state} handleListItemClick={this.handleListItemClick}/>
</div>
<div className="col-md-9 full-height">
<Map {...this.state}
handleMarkerClick={this.handleMarkerClick}/>
</div>
</div>
<Footer/>
</div>
);
}
but when i do this.searchVenues("yoga+coffee", "5") does not work, understandably so. What's the best or a better way to make it work? How do i access the function from another file ( component )?
I believe you want to declare your searchVenues func in App.js component like this:
searchVenues = (arg1, arg2) => {here is the body of your function}
... and pass it down to the ButtonComponent using props:
<ButtonComponent searchVenues={this.searchVenues}
Once you are in your stateless ButtonComponent, you can create a new function inside it if you want to avoid anonymous functions in your render (you can read on it here)
const searchVenues = (arg1, arg2) => {
return event => {props.searchVenues(arg1, arg2);}
}
... and add it to the onClick event:
<button onClick ={searchVenues('coffee+yoga', props.value)}>{props.value}</button>
If you want your buttons to live in another component, but receive handlers from another component, you can pass them down through props.
import React, { Component } from 'react'
import MyButton from './MyButton'
export default class App extends Component {
buttonClickHandler= () => {
alert('I have been clicked!')
}
render = () => (
<MyButton onClickHandler={this.buttonClickHandler} />
)
}
And here is the MyButton component file:
import React from 'react'
const MyButton = (props) => (
<button onClick={props.onClickHandler}>Click Me for an Alert!</button>
)
export default MyButton
You could either have searchVenues defined in your ButtonComponent or pass it to ButtonComponent as a prop. Then in your ButtonComponent render function you would do the same thing:
<button onClick ={() => this.searchVenues("yoga+coffee", "15")}>15</button>
or if it is passed as a prop
<button onClick ={() => this.props.searchVenues("yoga+coffee", "15")}>15</button>
I will also add that #Bryan is absolutely right about services. Lets say for instance you have a table in a database called Product. Well, you don't want to implement getAllProducts() in every component that needs a list of products. Instead, you would create a class called ProductService where getAllProducts() would be defined. Then, for any component that needs a list of products, you would import the ProductService class and call ProductService.getAllProducts().
Hope that helps.
If you want to execute methods from any component, and the result of those methods will change the global state of the app, you might benefit from using actions.
Whether you're using flux or redux architecture, actions can be triggered from any part of the app, and perform changes in the global state, this changes will be reflected on any component that is listening to this state.
https://reactjs.org/blog/2014/07/30/flux-actions-and-the-dispatcher.html
https://redux.js.org/basics/actions
I am trying to think the react way but I can't find a solution on how to invoke the .submit() method of the form component.
I have a material-ui Dialog where I have to pass the buttons via actions property. From this action component, I would like to invoke the .submit() method of the Form component, which is a child of the dialog.
Do I have to pass the formRef up to the Dialog to pass it then to the Actions, and how would I do that? Or is there any React way I am missing out on?
class FormDialog extends React.Component {
render() {
return (
<Dialog actions={<Actions />} >
<Form />
</Dialog>
)
}
}
const Actions = (props) => {
return (
<FlatButton
label="Submit"
onTouchTap={() => formRef.submit()}
/>
)
}
const Form = () => {
let formRef;
return (
<AutoForm
ref={ref => formRef = ref}
onSubmit={doc => db.save(doc)}
>
</AutoForm>
)
}
Any buttons inside a form that submit the form should be type="submit", and clicking on any of them will trigger the <form />'s onSubmit handler. There's no need to pass around a reference.
There's a few React-way notes here:
If you have to pass things "up" and then back "down" a component tree, you're probably not approaching the problem correctly.
Components should never call methods on other components.
Unless you really know when they're needed, refs to DOM elements should only be referenced inside the component owning the ref.
I have a form that has a submit button.
That form calls a function onclick that sets the state of something from false to true.
I then want to pass this state back to the parent so that if it is true it renders componentA but if it is false it renders componentB.
How would I do that in react?
I know I need to use state or props but not sure how to do it. also is this contradicting the one-way flow react principle??
ComponentA code:
<form onSubmit={this.handleClick}>
handleClick(event) {
this.setState({ decisionPage: true });
event.preventDefault();
};
Parent component that controls what it displays:
return (
<div>
{this.props.decisionPage ?
<div>
<LoginPage />
</div>
:
<div>
<Decision showThanks={this.props.showThanks}/>
</div>
}
</div>
)
Move handleClick to the parent and pass it to the child component as a prop.
<LoginPage handleClick={this.handleClick.bind(this)}/>
Now in the child component:
<form onSubmit={this.props.handleClick}>
This way submitting the form will update the state in parent component directly. This assumes you don't need to access updated state value in child component. If you do, then you can pass the state value back from the parent to the child as a prop. One-way data flow is maintained.
<LoginPage handleClick={this.handleClick.bind(this)} decisionPage={this.state.decisionPage}/>
Pass State as a Prop
I have recently learned a method that works great for changing state in a <Parent /> component from a <Child /> component.
This might not be the exact answer for this question, but it is surely applicable to this situation and other similar situations.
It works like this:
set the default STATE in the <Parent /> component - Then add the 'setState' attribute to the <Child />
const Parent = () => {
const [value, setValue] = useState(" Default Value ");
return (
<Child setValue={setValue} />
)
}
Then change the state(in Parent) from the Child component
const Child = props => {
return (
<button onClick={() => props.setValue(" My NEW Value ")}>
Click to change the state
</button>
)
}
When you click the button, the state in the <Parent /> component will change to whatever you set the state to in the <Child /> component, making use of "props".. This can be anything you want.
I Hope this helps you and other devs in the future.
In Parent Component:
getDatafromChild(val){
console.log(val);
}
render(){
return(<Child sendData={this.getDatafromChild}/>);
}
In Child Component:
callBackMethod(){
this.props.sendData(value);
}
Simple Steps:
Create a component called Parent.
In Parent Component create a method that accepts some data and sets
the accepted data as the parent's state.
Create a component called Child.
Pass the method created in Parent to child as props.
Accept the props in parent using this.props followed by method
name and pass child's state to it as argument.
The method will replace the parent's state with the child's state.
Here is an example of how we can pass data from child to parent (I had the same issue and use come out with this )
On parent, I have a function (which I will call from a child with some data for it)
handleEdit(event, id){ //Fuction
event.preventDefault();
this.setState({ displayModal: true , responseMessage:'', resId:id, mode:'edit'});
}
dishData = <DishListHtml list={products} onDelete={this.handleDelete} onEdit={(event, id) => this.handleEdit(event, id)}/>;
At the child component :
<div to="#editItemDetails" data-toggle="modal" onClick={(event)=>this.props.onEdit(event, listElement.id) }
className="btn btn-success">
In React you can pass data from parent to child using props. But you need a different mechanism to pass data from child to parent.
Another method to do this is to create a callback method. You pass the callback method to the child when it's created.
class Parent extends React.Component {
myCallback = (dataFromChild) => {
//use dataFromChild
},
render() {
return (
<div>
<ComponentA callbackFromParent={this.myCallback}/>
</div>
);
}
}
You pass the decisionPage value from the child to the parent via the callback method the parent passed.
class ComponentA extends React.Component{
someFn = () => {
this.props.callbackFromParent(decisionPage);
},
render() {
[...]
}
};
SomeFn could be your handleClick method.
if your parent component is a functional component you can now use the use context way. Which involves passing the ref to the object and the ref to the stateChanging method. What this will allow you to do is change state from parrent in child and also ref tht state while remaining synced with Parent State. You can learn more about this in a youtubeVideo by codedamn titled 'React 16.12 Tutorial 20: Intro to Context API' and 'React 16.12 Tutorial 21: useContext'
This works exactly what I wanted. But in case of set of data with say 50 records with (customer_id, customer_name) as values to be updated from child to parent, then this lags. Do the setState using React.useEffect in child component
i have same problem and so performed this code :
in Parent
const PARENT = () => {
const [value, setValue] = useState("....");
return (
)
}
in Child
const CHILD = props => {
return (
<button onClick={() => props.setValue("....")}>
Click to change the state
</button>
)
}