React - passing ref to parent - javascript

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.

Related

React Functional Custom Button Not Registering A Click

So here is my functional Button component
export const MyButton = (props) => {
return (
<button className="myButton">{props.text}</button>
)
}
And I used it in another page like
<MyButton text="Update" onClick={this.checkIt} />
Where that checkIt function is just console logging the state of the class I used it in. But for some reason, it isn't registering the click at all. How can I go about solving it? I implemented in a similar way for input field where I used the props.onChange(event.target.value) and that is working properly.
You should attach events in child components, otherwise it's not bind to actual UI at all!
Solution 1:
export const MyButton = ({text, ...others}) => {
return (
<button className="myButton" {...others}>{text}</button>
)
}
Solution 2:
export const MyButton = ({text, onClick}) => {
return (
<button className="myButton" onClick={onClick}>{text}</button>
)
}
You can't use onClick on MyButton component directly because MyButton component isn't a Native DOM element it is just another REACT component and onClick is treated as another property MyButton Component. What you need to do is, inside of MyButton component pass the onClick component to DOM element Button
export const MyButton = (props) => {
return (
<button className="myButton" onClick={props.onClick}>{props.text}</button>
)
}

Passing information from child component to parent

This is a follow up question to this question:
Call child method from parent
I am using React > 16.8 with function components and hooks.
I got a parent component which manages a component to display items and add a new item. The list of items is at the parent component. The way the items are added are by a "+" button which opens a new modal window (which is a component of its own), and inside there's a form that the user can insert the details of the new items.
const registerFormRef = useRef();
<Modal
isOpen={isFormOpen}
onCancel={() => setIsFormOpen(false)}
onSubmit={() => { registerFormRef.current.onSubmitForm(); setIsFormOpen(false) }}
titleText="Register Tenant">
<AddItem onAddItem={AddNewItem} ref={registerFormRef}></RegisterTenant>
</Modal>
The AddNewItem is a callback which adds the new item to the list. The modal has an "OK" button which serves as a submit button. It belongs to the parent modal component, not the AddItem child.
The method in the child component:
useImperativeHandle(ref, () => (
{
onSubmitForm()
{
setIsLoading(true);
const newItem = {
name: formSettings["name"].value,
description: formSettings["description"].value,
area: "",
category: ""
}
props.onAddItem(newItem);
setIsLoading(false);
}
}));
I had an issue of getting the information from the child component which holds the form to the parent component, since the submit button as I said, belongs to the modal, I had to somehow call the callback from inside the child form. I have used the accepted answer in the linked question above. It works, but the comment says it's not a good practice passing information like that. Is there another way of passing the information from the child form to the parent component?
The correct way is to store the form data in the parent i.e the component rendering the modal. To do that you could define a state and provide an onChange handler to it. Once you do that on any change in input the AddItem component must notify its parent by calling the onChange method
const App = () => {
const [data, setData] = useState();
const handleChange=(newData) => {
setData(newData);
}
const onSubmit = () => {
// use data to do whatever you want with the formData
console.log(data);
setIsFormOpen(false)
}
...
return (
<Modal
isOpen={isFormOpen}
onCancel={() => setIsFormOpen(false)}
onSubmit={onSubmit}
titleText="Register Tenant">
<AddItem onAddItem={AddNewItem} handleChange={handleChange} ref={registerFormRef}></RegisterTenant>
</Modal>
)
}

Use of onClick event handler in React

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>
);
}
}

Modify render function of external React component (with no access)

I have to use a react component that I cannot modify. It's from an external source, due to changes. This could also be a component from a npm package that I import. This is what it looks like, a simple button:
class Button extends React.Component {
// ... more code above
render() {
const { onClick, disabled, children} = this.props;
return (
<button className={this.getClasses()} onClick={onClick} disabled={disabled}>
{this.props.symbol && <Icon symbol={this.props.symbol} />}
{children}
</button>
);
}
}
How can I add some functionality with no access to the file (I can create my own component that extends the button)? For example, I want a type prop in there. I thought I can just create a <ButtonExtend onClick={resetState} type="button />.
How can I do this? Ideally I would like to make this even more flexible, so I can also do: <ButtonExtend onClick={resetState} type="submit" name="extended button" />.
I would expect the html to render all the properties from <Button> with my additional html attributes. So I want to use the functionality of the original and my additional props. Or it this not even possible, to change the render method of another component, if the component doesn't make it possible?
Although public methods and properties of a component are accessible by refs (https://reactjs.org/docs/refs-and-the-dom.html) the pattern are you looking for is High Order Components (HOC, https://reactjs.org/docs/higher-order-components.html)
Unless a component was designed for customization, there is no straightforward way to do this.
Button is an example of badly designed component because it doesn't accept additional props. An issue and PR could be submitted to the repository in order to address original problem.
In extended component, this can be fixed by passing props from extended component.
Parent render result could be modified:
class ButtonExtend extends Button {
// ... more code above
render() {
const button = super.render();
const { symbol, children, ...props } = this.props;
return React.cloneElement(button, {
children: [
symbol && <Icon symbol={symbol} />,
...children
],
...props
});
}
If an element that needs to be modified is nested, this may become messy and result in unnecessarily created elements.
A cleaner way is to paste render in extended component and modify it:
class ButtonExtend extends Button {
// ... more code above
render() {
const { symbol, children, ...props } = this.props;
return (
<button className={this.getClasses()} {...props}/>
{symbol && <Icon symbol={symbol} />}
{children}
</button>
)
}
}
This way it can be used as
<ButtonExtend onClick={resetState} type="submit" name="extended button" />

How to pass state back to parent in React?

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>
)
}

Categories