I have some action buttons in parent components. On click of one of such buttons, I would like to trigger a function in the child component. Currently, I am trying to implement it using useRef hook. But the solution seems tedious and also gives me warning:
My current code looks like:
import React, {useContext, useEffect, useState, useRef} from 'react';
const ParentComponent = ({...props})=> {
const myRef = useRef();
const onClickFunction = () => {
if(myRef.current) {
myRef.current.childFunction();
}
}
return (
<ChildComponent ref = {myRef}/>
);
}
Child component
const ChildComponent = (({}, ref,{ actionButtons, ...props}) => {
const [childDataApi, setChildDataApi] = useState(null);
const childFunction = () => {
//update childDataApi and pass it to parent
console.log("inside refreshEntireGrid");
}
});
Firstly, is there a better solution then trying to trigger childFunction from parent ? For this I am following this solution:
Can't access child function from parent function with React Hooks
I tried adding forward ref but that threw error as well.
I also found out that lifting the state up could be another solution as well. But I am not able to understand how to apply that solution in my case. Can someone please help me with this.
The warning says you were using forwardRef so with your snippet const ChildComponent = (({}, ref, { actionButtons, ...props }) => { .... } I'll assume this is a typo in your question and you were actually doing const ChildComponent = React.forwardRef(({}, ref,{ actionButtons, ...props }) => { .... }).
The issue here, and the warning message points this out, is that you are passing a third argument to forwardRef when it only consumes two. It seems you destructure nothing from the first props argument. From what I can tell you should replace the first argument with the third where it looks like you are doing some props destructuring.
const ChildComponent = React.forwardRef(({ actionButtons, ...props }, ref) => { .... }
From here you should implement the useImperativeHandle hook to expose out the function from the child.
const ChildComponent = React.forwardRef(({ actionButtons, ...props }, ref) => {
const [childDataApi, setChildDataApi] = useState(null);
const childFunction = () => {
// update childDataApi and pass it to parent
console.log("inside refreshEntireGrid");
}
useImperativeHandle(ref, () => ({
childFunction
}));
...
return ( ... );
});
In the parent component:
const ParentComponent = (props) => {
const myRef = useRef();
const onClickFunction = () => {
myRef.current?.childFunction();
}
return (
<ChildComponent ref={myRef}/>
);
}
Something else you can try is to pass a prop to the child to indicate that the button has been clicked and use useEffect in the child component to do something when that value changes.
const Child = props => {
useEffect(() => TriggeredFunc(), [props.buttonClicked]);
const TriggeredFunc = () => {
...
}
return '...';
}
const Parent = () => {
const [buttonClicked, setButtonClicked] = useState(0);
const onClick = e => {
setButtonClicked(buttonClicked++);
}
return <>
<button onClick={onClick}>My Button</button>
<Child buttonClicked={buttonClicked} />;
</>
}
Related
I am a little confused as to how to update state between parent and child components. I know state needs to be lifted up which is why I have added it to the parent component. So I want to update the boolean value in the child component(can this be done?). I have tried as below but get the error: Cannot invoke an object which is possibly 'undefined'. This expression is not callable.Type 'Boolean' has no call signatures.
Stackblitz example: https://stackblitz.com/edit/react-ts-hzssfh?file=Child.tsx
Parent
import React from 'react';
import Child from '../components/Child';
const Parent: React.FunctionComponent = () => {
const [visible, setVisible] = React.useState<boolean>(false);
const toggle = () => {
setVisible(!visible);
};
return (
<button onClick={toggle}>toggle</button>
<Child visible={visible} />
)
};
export default Parent;
Child
import React from 'react';
interface Icomments {
visible?: boolean;
}
const Child: React.FunctionComponent<Icomments> = (props: Icomments) => {
const handleClick = () => {
props.visible(false);
};
return (
<button onClick={handleClick}>Hide</button>
)
}
export default Child;
The child needs the function that sets the state - not the state value. So you need to pass down to the Child the setVisible function as a prop.
<Child setVisible={setVisible} />
const Child = ({ setVisible }) => (
<button onClick={() => { setVisible(false); }}>Hide</button>
);
my code like this:
Info component:
import {
getAttachData,
} from '#src/actions/creators/account'
const Info: React.FC = () => {
const info = useSelector<any, Account>(state => state.getIn(['account', 'info']).toJS())
const list = useSelector<any, Data[]>(state => state.getIn(['account', 'list']).toJS())
const attach = useSelector<any, AttachData[]>(state => state.getIn(['account', 'attach']).toJS())
...
const handleChange = ({ select }) => {
dispatch(getAttachData({v: select}))
}
const Template = (params) => {
return (
<div>
<BaseSelect onChange={(val) => handleChange(val)} list={list} />}
</div>
)
}
return (
...
<Template data={info} />
{attach.map((child, cidx) => (<Template data={child} />))}
)
}
export default Info
BaseSelect component:
const BaseSelect: React.FC<Props> = props => {
const [selectId, setSelectId] = useState('')
const { list } = props
useEffect(() => {
if (!isEmpty(list)) {
...
}
console.log('init')
}, [])
const handleChange = (value) => {
setSelectId(value)
props.onChange({
select: value,
})
}
return (
<Select
data={list}
value={selectId}
onChange={handleChange}
/>
)
}
export default BaseSelect
when excute handleChange event in BaseSelect component, the props.onChange function will call handleChange event in info component, and dispatch http request getAttachData which will change attach data in redux store, but useEffect in BaseSelect component will also excute and in console will print 'init' two times.
console:
It's because your Template component re-creates every time when redux store is changing.
Just move Template component outside the Info component.
I need to call a function in a child component from a parent component with React Hooks.
I was trying to adapt this question to my use case React 16: Call children's function from parent when using hooks and functional component
but I keep getting the error
TypeError: childRef.childFunction is not a function
My parent component is like this:
import React, { useRef, useEffect } from 'react';
import Child from './child'
function Parent() {
const parentRef = useRef()
const childRef = useRef()
const callChildFunction = () => {
childRef.current(childRef.childFunction())
}
useEffect(() => {
if (parentRef && childRef) {
callChildFunction();
}
}, [parentRef, childRef])
return (
<div ref={parentRef} className="parentContainer">
PARENT
<Child ref={childRef}/>
</div>
);
}
export default Parent;
My child component is like this:
import React, { forwardRef, useImperativeHandle } from 'react';
const Child = forwardRef(({ref}) => {
useImperativeHandle(ref, () => ({
childFunction() {
console.log("CHILD FUNCTION")
}
}));
return (
<div className="childContainer">
CHILD
</div>
);
})
export default Child;
What am I doing wrong?
I think this is your problem
childRef.current(childRef.childFunction())
childRef.current isn't a function. Also childRef.childFunction() is run first, which also isn't a function.
childRef.current.childFunction should be a function, try childRef.current.childFunction() instead of childRef.current(childRef.childFunction())
From the docs on useImperativeHandle check out the usage of inputRef.current.focus():
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
In this example, a parent component that renders <FancyInput ref={inputRef} /> would be able to call inputRef.current.focus().
Edit based on comment for future visitors:
const Child = forwardRef(({ref}) => {
should be
const child = forwardRef(({}, ref) => {
I have a Parent component and couple of child components. I need to disable or enable the button in the parent based on the ErrorComponent. If there is an error then I disable the button or else I enable it. I believe we can pass callbacks from the child to parent and let the parent know and update the button property. I need to know how to do the same using react hooks? I tried few examples but in vain. There is no event on error component. If there is an error (props.errorMessage) then I need to pass some data to parent so that I can disable the button. Any help is highly appreciated
export const Parent: React.FC<Props> = (props) => {
....
const createContent = (): JSX.Element => {
return (
{<ErrorPanel message={props.errorMessage}/>}
<AnotherComponent/>
);
}
return (
<Button onClick={onSubmit} disabled={}>My Button</Button>
{createContent()}
);
};
export const ErrorPanel: React.FC<Props> = (props) => {
if (props.message) {
return (
<div>{props.message}</div>
);
}
return null;
};
I'd use useEffect hook in this case, to set the disabled state depending on the message props. You can see the whole working app here: codesandbox
ErrorPanel component will look like this:
import React, { useEffect } from "react";
interface IPropTypes {
setDisabled(disabled:boolean): void;
message?: string;
}
const ErrorPanel = ({ setDisabled, message }: IPropTypes) => {
useEffect(() => {
if (message) {
setDisabled(true);
} else {
setDisabled(false);
}
}, [message, setDisabled]);
if (message) {
return <div>Error: {message}</div>;
}
return null;
};
export default ErrorPanel;
So depending on the message prop, whenever it 'exists', I set the disabled prop to true by manipulating the setDisabled function passed by the prop.
And to make this work, Parent component looks like this:
import React, { MouseEvent, useState } from "react";
import ErrorPanel from "./ErrorPanel";
interface IPropTypes {
errorMessage?: string;
}
const Parent = ({ errorMessage }: IPropTypes) => {
const [disabled, setDisabled] = useState(false);
const createContent = () => {
return <ErrorPanel setDisabled={setDisabled} message={errorMessage} />;
};
const handleSubmit = (e: MouseEvent) => {
e.preventDefault();
alert("Submit");
};
return (
<>
<button onClick={handleSubmit} disabled={disabled}>
My Button
</button>
<br />
<br />
{createContent()}
</>
);
};
export default Parent;
How can I pass props to a child component when the component is a variable. In the following code, I need to pass the prev function as a prop to the step. Thanks.
import React, {useState} from 'react';
const Wizard = (props)=>{
const [step] = useState(0);
const CurrStep = props.steps[step];
const prev = ()=>{
console.log('prev called')
}
return (
<div>
{// need to pass prev as a prop to the CurrStep component}
{CurrStep }
</div>)
}
export default Wizard
Wizard.propTypes = {
header: PropTypes.func.isRequired,
steps: PropTypes.array.isRequired,//array of functional components
wizardContext: PropTypes.object.isRequired,
onComplete: PropTypes.func.isRequired
};
you can spread props inside the CurrStep component like this
return <CurrStep {...props} />;
here's a codesandbox demo of the code below
import React, { useState } from "react";
const Wizard = props => {
const [step] = useState(0);
const Comp1 = props => <div>{props.a}</div>;
const Comp2 = props => <div>{props.a}</div>;
const comps = [Comp1, Comp2];
const CurrStep = comps[step];
// this is just for demo, you can just pass props straight from Wizard
props = { a: "a", ...props };
return <CurrStep {...props} />;
};
export default Wizard;
This was my mistake, I was passing in an array like this:
[, ] instead of
[Step1, Step2]
Sorry to waste your time.
// adding (props) =>
const CurrStep = (...props) => props.steps[step](...props);
const prev = () => {
console.log('prev called')
}
return (
<div>
{CurrStep(prev)}
</div>
)