react functional components call child component method - reactjs

I have ModalPopup component as a parent and I am rendering cart and orderform as child components based on some conditions. For each component I have separate buttons in modalPopup which gets rendered once child componenent is changed in the modalPopUp.
I want to call orderForm component method from ModalPopUp component on place order button click.
While placing order getting below error in console.
index.js:1 Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
<ModalPopUp
show={isModalVisible}
hide={hideModal}
onCheckout={cartCheckout}
onOrderPlaced={placeOrder}>
{
isCheckoutDone? <OrderForm ref={orderFormRef}/> :<Cart selectedProducts={selectedProducts}/>
}
</ModalPopUp>
Refer stackblitz example code here.

To do this you need to use forwardRef along with useImperativeHandle
https://reactjs.org/docs/forwarding-refs.html
https://reactjs.org/docs/hooks-reference.html#useimperativehandle
import { forwardRef, useImperativeHandle, useRef } from 'react';
const Child = forwardRef((props, ref) => {
useImperativeHandle(ref, { func: () => { ... } }, []);
return (...);
});
const Parent = () => {
const childRef = useRef(null);
const handleOnSomething = () => {
if (childRef.current) {
childRef.current.func();
}
}
return (
<Child ref={childRef} onSomething={handleOnSomething}
)
}

Related

Is it an anti-pattern to pass functions from parent to child in React, so that the child can update the parent's state?

What are the considerations behind deciding between passing the out of the box setWhatever from useState() down to a child (see case one below) versus creating a custom handleWhatever function to pass to the child, where the handleWhatever function uses setWhatever within it (see case two below)?
Definitely React is about passing state down from parent to child. But after reiterating that fact, it seems like we are always left with countless examples where a function is passed from parent to child for the purpose of keeping a parent aware of a certain value within the child. My question is about such cases where a function needs to be passed down to a child. (If all such cases are misguided and functions being passed down for the sake of updating a parent is an anti-pattern then it would be useful to know what to do instead-- global variable?).
Case #1
// Test.js (parent, case one)
import React, { useState } from 'react';
import DetermineValue from './DetermineValue';
const Test = () => {
const [importantValue, setImportantValue] = useState();
console.log(importantValue);
return <DetermineValue setImportantValue={setImportantValue} />;
};
export default Test;
// DetermineValue.js (child, case one)
import React from 'react';
const DetermineValue = ({ setImportantValue }) => {
return (
<>
...
onClick={() => {
setImportantValue('Important Data');
}}
...
</>
);
};
export default DetermineValue;
Case #2
// Test.js (parent, case two)
import React, { useState } from 'react';
import DetermineValue from './DetermineValue';
const Test = () => {
const [importantValue, setImportantValue] = useState();
const handleSetImportantValue = (importantValueQuickPass) => {
setImportantValue(importantValueQuickPass);
};
console.log(importantValue);
return <DetermineValue handleSetImportantValue={handleSetImportantValue} />;
};
export default Test;
// DetermineValue.js (child, case two)
import React from 'react';
const DetermineValue = ({ handleSetImportantValue }) => {
return (
<>
...
onClick={() => {
handleSetImportantValue('Important Data');
}}
...
</>
);
};
export default DetermineValue;
Passing setState to a child component is the way I'd go if there's no other logic included. But this warning is not about child component calling the setState passed from parent. It's about calling it during render phase. But your code in the edited version of the question shouldn't give this error. Please refer to the docs about the warning.

Call function in components that are rendered by a factory component

I have the following problem:
I have a react functional component A (the parent component)
In the parent Component A, a factory component named < Component /> creates different Components such as Component B,C,D by using plain JSON objects.
What I want to achieve:
Component B,C and D shall all implement a handlerFunction with specific code on their own. So the handlerFunction is not provided by the parent component, it is implemented by the Components B,C and D on their own.
I want to call the specific handlerFunction of each Component B,C, and D.
How is this possible ?
Right, functional components, on their own, cannot be assigned a react ref, but you can forward the ref or pass a ref as a named prop.
In the class-based component example you have something like
class ComponentA extends Component {
handlerFunction = () => {
console.log("A handler function");
};
render() {
return ...;
}
}
and to invoke the handlerFunction, attach the ref and call ref.current.handlerFunction() in your code
const someFunction = () => {
...
refA.current.handlerFunction();
...
}
...
<ComponentA ref={refA} />
For a functional component you can forward the ref and use the useImperativeHandle hook to "connect" the ref to the internal handler function
const ComponentB = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
handlerFunction
}));
const handlerFunction = () => {
console.log("B handler function");
};
return ...;
});
and to invoke the handlerFunction, same thing, call ref.current.handlerFunction()
const someFunction = () => {
...
refB.current.handlerFunction();
...
}
...
<ComponentB ref={refB} />

React child component does not re-render when props passed in from parent changes

I have a simplified react structure as below where I expect MyGrandChildComponent to re-render based on changes to the 'list' property of MyParentComponent. I can see the list take new value in MyParentComponent and MyChildComponent. However, it doesnt even hit the return function of MyGrandChildComponent. Am i missing something here?
const MyGrandChildComponent = (props) => {
return (
<div>props.list.listName</div>
);
};
const MyChildComponent = (props) => {
return (
<div><MyGrandChildComponent list={props.list}/></div>
);
}
const MyParentComponent = (props) => {
const list = { listName: 'MyList' };
return (
<div><MyChildComponent list={list} /></div>
);
}
In your MyParentComponent, the list is not a state variable and as such changing it will not even cause a re-render. If you absolutely want that when ever you change the value of list it re renders, then you will want to bring state to your functional component and the way to do that is to use hooks.
In this case your parent component will be something like below
import React, {useState} from 'react'
const MyParentComponent = (props) => {
const [list, setList] = useState({ listName: 'MyList' });
return (
<div><MyChildComponent list={list} /></div>
);
}
then at the child component you render it as I suggested in the comment above.
The parent needs to hold the list as a state variable and not just as a local variable. This is because react rerenders based on a state or prop change and at the parent you can only hold it in the state. With this when the value of list changes there will be a re-render which will then propergate the change to the children and grandchildren.
Also the only way of maintaining state in a functional component is to use hooks.
const MyGrandChildComponent = (props) => {
return (
<div>{props.list.listName}</div>
);
};
You forgot the {} around props.list.listName

React rerendering children in functional component despite using memo and not having any prop changed

I have an Icon component that draw an icon and which is blinking because the parent is making it rerender for nothing. I don't understand why this is happening and how to prevent this.
Here is a snack that shows the issue.
We emulate the parent changes with a setInterval.
We emulate the icon rerendering by logging 'rerender' in the console.
Here is the code:
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
// or any pure javascript modules available in npm
let interval = null
const Child = ({name}) => {
//Why would this child still rerender, and how to prevent it?
console.log('rerender')
return <Text>{name}</Text>
}
const ChildContainer = ({name}) => {
const Memo = React.memo(Child, () => true)
return <Memo name={name}/>
}
export default function App() {
const [state, setState] = React.useState(0)
const name = 'constant'
// Change the state every second
React.useEffect(() => {
interval = setInterval(() => setState(s => s+1), 1000)
return () => clearInterval(interval)
}, [])
return (
<View>
<ChildContainer name={name} />
</View>
);
}
If you could explain me why this is happening and what is the proper way to fix it, that would be awesome!
If you move const Memo = React.memo(Child, () => true) outside the ChildContainer your code will work as expected.
While ChildContainer is not a memoized component, it will be re-rendered and create a memoized Child component on every parent re-render.
By moving the memoization outside of the ChildContainer, you safely memoize your component Child once, and no matter how many times ChildContainer will be called, Child will only run one time.
Here is a working demo. I also added a log on the App to track every re-render, and one log to the ChildComponent so you can see that this function is called on every re-render without actually touching Child anymore.
You could also wrap Child with React.memo directly:
import * as React from "react";
import { Text, View, StyleSheet } from "react-native";
// or any pure javascript modules available in npm
let interval = null;
const Child = React.memo(({ name }) => {
//Why would this child still rerender, and how to prevent it?
console.log("memoized component rerender");
return <Text>{name}</Text>;
}, () => true);
const ChildContainer = ({ name }) => {
console.log("ChildContainer component rerender");
return <Child name={name} />;
};
export default function App() {
const [state, setState] = React.useState(0);
const name = "constant";
// Change the state every second
React.useEffect(() => {
interval = setInterval(() => setState(s => s + 1), 1000);
return () => clearInterval(interval);
}, []);
console.log("App rerender");
return (
<View>
<ChildContainer name={name} />
</View>
);
}

How do I call a method within a Child component from the Parent in React Native

How do I call a method within a Child component from the Parent in React Native? What I essentially want to do is emulate what componentDidMount() does for class components in a functional component.
I've been getting the error "Function components cannot be given refs" and that I may want to use React.ForwardRef().
ps. idk how i would go about reformatting the child observer, pardon the bad formatting
class Dashboard extends Component {
constructor(props) {
super(props);
this.load = React.createRef();
componentDidMount() {
this.load.current.loadAudio();
}
render(){
latestEP.query = ref => ref.orderBy("id", "desc").limit(1);
return(
{latestEP.docs.map(doc => (
<DashboardItem key={doc.id} doc={doc} ref={this.load} />
))}
)
}
}
const DashboardItem = observer(({ doc }) => {
function loadAudio(){
return console.log("works")}
return (// stuff that requires loadAudio to run first)
})
You can achieve that by using useImperativeHandle hook. Please check this out:
https://reactjs.org/docs/hooks-reference.html#useimperativehandle
Wrap DashItem in forwardRef and implement useImperativeHandle hook like below:
const DashItem = React.forwardRef(({doc}, ref) => {
useImperativeHandle(ref, () => ({
loadAudio: () => {
return console.log("works");
}
}));
...
The error "Function components cannot be given refs" should be self-explanatory: you need to change DashItem to be a class component instead of a functional component.

Resources