Call function in components that are rendered by a factory component - reactjs

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

Related

react functional components call child component method

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

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.

React.cloneElement vs render props pattern

I'm learning React and trying to figure out the best way to dynamically add props. I know two approaches how to do that, but cannot understand which one is better and faster.
First way is to use React.cloneElement api
const Parent = ({ children }) => {
const child = React.cloneElement(children, { newProp: 'some new prop' });
return child;
};
const Child = ({ newProp }) => <div>{newProp}</div>;
const App = () => (
<Parent>
<Child />
</Parent>
);
The second way is to use "render props" pattern, described on official React site: Render Props
const Parent = ({ render }) => {
const newProp = 'some new prop';
return render(newProp);
}
const Child = ({ newProp }) => <div>{newProp}</div>;
const App = () => (
<Parent render={props => <Child newProp={props} />} />
);
Both approaches give same results, but I don't know what is going on under the hood and which way to use.
React.cloneElement is a helper that's usually used to pass inline-props to avoid polluted codes. Instead of passing a prop down like this:
return <Child propA={myProps.propA} propB={myProps.propB} />
You can do this:
return React.cloneElement(Child, {...myProps})
Which is almost the same as:
return <Child {...myProps} />
The only difference here is that cloneElement will preserve previously attached refs.
Now renderProps is a technique to reuse stateful logic and has different applications than cloneElement. The first will help you with props manipulation, the second is an equivalent to High Order Components and is used whenever you want to reuse some logic to dinamically inject props into your children:
class Animation extends Component{
state = {}
componentDidMount(){/*...*/}
componentDidUpdate(){/*...*/}
render(){
const { customProps } = this.state
const { children } = this.props
return children(customProps)
}
}

Shallow render a functional stateless component -react

I have a functional component in React defined as follows
const MyComponent = ({
prop1,
prop2,
prop3
}, param1 , param2) => {
return [
//list of spans
]
}
In the spec file, I used shallow to render the component
const fakeObj = {
prop1:value,
prop2:value,
prop3:value
}
shallow(<MyComponent {...fakeObj} param1={something} param2={something} />)
However, when I console.log the params in the MyComponent, I get {} and undefined for param1 and param2 respectively,while the fakeObj is received fine.Is there any way where we can shallow render the component whilst passing object as one of the parameters?
When I just call the component from the spec file as a function ie
MyComponent({fakObj},param1,param2)
,I get the correct values for params,but not able to find the span elements properly using enzyme.
React functional component, maybe the function (function to create component) accepts only one parameter to get its props. So you can't define functional component in the way (define multiple parameters) what you did. You have to define it like following.
let MyComponent = (props) => {
// props is an object, it contains all props passed by component call.
// So you can get property value as props.propName
return 'something' // what you like to return.
}
If you use the component like bellow
<MyComponent prop1="Propone 1 Value" prop2="Prop 2 Value" prop3="Prop 3 Value" />
And console props inside your component like
let MyComponent = (props) => {
console.log(props);
return 'something' // what you like to return.
}
You will get all your passed properties as function argument (as props parameter) as an object like below.
{
prop1:"Propone 1 Value",
prop2:"Propone 2 Value",
prop3:"Propone 3 Value"
}
Now come to your requirement. You can create your component like following
let Home = (props) => {
let {fakeObj, param1, param2} = props;
return [
//list of spans
]
}
And call component like this
const fakeObj = {
prop1:value,
prop2:value,
prop3:value
}
shallow(<MyComponent fakeObj = {fakeObj} param1={something} param2={something} />)

Passing props to dynamic react component

So I have the below code to load a custom component called foo. Loading of the component works fine, but props arent passing to it like I would prefer
Container component
....
const id= foo
React.createElement(LoadComponent(id, attributes))
...
Custom component
export const LoadComponent = (id, attributes) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
How do I pass attributes prop to the Component in this case? I keep getting render exceptions.
Here is a simplified demo: https://codesandbox.io/s/zrw7x1zrrp
props are being passed to the component as the second parameter of createElement
const id = "foo";
const LoadComponent = props => {
return props.id;
};
ReactDOM.render(
React.createElement(LoadComponent, { id }, null),
document.getElementById("root")
);
React.createElement(component, props, ...children)
https://reactjs.org/docs/react-without-jsx.html
It's a bit confusing what you are trying to do. If you are using JSX you shouldn't need to invoke React.createElement.
If you insist of doing it without JSX though, React.createElement can take 3 parameters as per React's API. So, in your case your code will be React.createElement(LoadComponent, { id, attributes }, null) where the third parameter is the children.
Now, the id and attributes are accessible from within the props object of your custom component. So you have two options:
Destructure the props object:
export const LoadComponent = ({ id, attributes }) => {
/*This will load up foo.js*/
const Component = require(`./${id}`);
return Component;
};
Use the props object directly:
export const LoadComponent = (props) => {
/*This will load up foo.js*/
const Component = require(`./${props.id}`);
return Component;
};

Resources