Lambda function inside render props - reactjs

I am using render props:
<MyComponent>
{myProps => (
<button onClick={({ target: { value } }) => myProps.myFunction(value)} />
)}
</MyComponent>
However the onClick={({ target: { value } }) => myProps.myFunction(value)} is not optimized, as it creates a new function on each render.
My naive solution is:
const handleClick = props => ({ target: { value } }) => props.myFunction(value);
<MyComponent>
{myProps => (
<button onClick={handleClick(myProps)} />
)}
</MyComponent>
But if I understand well, unless handleClick is somehow memoized then it's quite useless.
Is there a nice way to optimize lambda functions in render props?
Note: Bonus point if it only uses stateless functional components, optionally with recompose.

Related

'Maximum update depth exceeded' error when trying to use custom function passed as props in 'onClick' of button

I am trying to use a pass a function as props declared in App.js to handle a state variable in App.js in order to add items to a cart component but get this error as soon as I add the function to the onClick field of the "Add" button in my product component(at the end of post):
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
My App.js looks like this:
const App = () => {
const [cartItems, setCartItems] = useState([]);
const handleAddProduct = (product) => {
//some logic to add product to cartItems list here
}
return(
<Box className="App">
<AppRoutes handleAddProduct={handleAddProduct} cartItems={cartItems}/>
</Box>
);
}
Im passing the function and the state variable as props to my routes component so I can access it in my Products page:
const AppRoutes = ({ handleAddProduct, cartItems }) => {
return (
<Routes>
<Route exact path="/alldrip" element={<ProductsPage handleAddProduct={handleAddProduct} />} />
</Routes>
)}
And in my products page the function gets passed as props again to another component:
const ProductsPage = ({ handleAddProduct }) => {
return (
<AllProducts handleAddProduct={handleAddProduct} />
)}
And then I pass the function one last time in AllProducts to an individual Product component: ( I seperated the components this way so it is easier for me to read)
const AllProducts = ({ handleAddProduct }) => {
return (
{products.map(product => {
return (
<Product handleAddProduct={handleAddProduct} product={product} />
)
})}
)}
The products load fine but the app crashes with the error as soon as I add the function to the "Onclick" of the add to cart button:
const Product = ({ handleAddProduct, product }) => {
return (
<Heading>{product.name}</Heading>
<Text >{product.material}</Text>
<Text>{product.price} </Text>
<Button onClick={handleAddProduct(product)} >Add to Cart</Button>
)}
If I remove the function the app stays alive !
I'm not understanding why the error states setState is getting called repeatedly.
onClick={handleAddProduct(product)}
This should probably only be
onClick={() => handleAddProduct(product)}
otherwise you're calling the handleAddProduct method on render directly and not on click.
You call your handleAddProduct directly in your jsx that re-renders the component that directly call handleAddProduct and so on ...
You can try
onClick={() => handleAddProduct(product)}
A better approach is to avoid anonymous functions so in your Product component
const Product = ({ handleAddProduct, product }) => {
const onAddProduct = (product) => {
handleAddProduct(product)
}
return (
...
<Button onClick={onAddProduct}>Add to Cart</Button>
)
)}

Handle children.props

so, in ParentComponent I have
<Component dropDownContent={<DropDownContent content={array} onSelect={handleSelect} />} />
my DropDownContent looks something like this
return (<ul>
{content.map((item) =>
{ return <li><button onClick={()=> onSelect(array.id)}>{array.name}</button></li>}
)}
</ul>)
Can I some how do something with the onSelect inside Component even if I add DropDownContent as a prop to Component?
Thanks :)
What I understand from your question is that you want to pass a function from the parent component to the child component. And when a local function inside child component is clicked, you want to call that passed function. if yes, then this is your solution:
Note: I do not know exactly what code you wrote and what your component consists of. So I will give you the answer by giving a simple example to fully understand the solution.
In your parent component:
export const ParentComponent = props => {
const handleSelect = () => console.log(`do something here`);
return (
<Component
dropDownContent={
<DropDownContent
content={array}
onSelect={() => handleSelect()}
/>}
/>
)
}
And in your child component you need to receive the passed function like below:
export const ChildComponent = props => {
const handlePassedFunction = () => props.onSelect?.();
return (<ul>
{content.map((item) => {
return <li>
<button onClick={() => handlePassedFunction(array.id)}>{array.name}</button>
</li>
}
)}
</ul>)
}

How to render based on a condition in Typescript?

Say we have a component, with the following definition:
export const MentForm: React.FC<IProps> = (props: IProps) => {
It's return is a div with a load of HTML, some of which is the following:
<div className="form">
<MentFormButton {...fProps} />
<FlyComp
user={props.user}
onClick={props.onShowFly(props.user)}
/>
{showFlyForm (
<FlyForm
onFlyed={() => {
if (props.onFlyClicked) {
props.onFlyClicked(flies)
}
setShowFlyForm(false)
}}
onFlyFail={() => {
setShowFlyForm(false)
}}
user={flies}
/>
)}
</div>
Now, when we click the button, onShowFly gets triggered
We have another TSX file that, within a Test functional component, there is a return with a <div> and that has the following:
<MentForm
user={new User}
onCancelClick={() => {
if (props.user) {
wait()
}
}}
onShowFly={() => {// HERE //}}
/>
Where I've wrote "HERE", I want to be able to have the stuff in curly braces in the MentForm component to activate.... but I don't know how to do this....
Within the component that has your showFlyForm, you can use useEffect to fire off a function whenever the state changes and then just check for showFlyForm === true or whichever logic you use to check for showing the form.
Ex:
useEffect(() => {
showFlyForm && [your HERE function]
}, [showFlyForm])

Strange behavior of parametrised function passed as a prop in React

I am probably not seeing some obvious thing right here, but i feel pretty stuck with this one.
I have a function
public handleTest = (testNum: number) => {
console.log(testNum);
};
And the following case: I have a component to which I want to pass this function to further use it there onCLick event.
<Controls handleTest={() => this.handleTest}>
<Button label="Test1" clicked={() => this.handleTest(42)} />
</Controls>
The child component is the following:
interface IProps {
handleTest: (type: number) => void;
}
class Controls extends React.Component<IProps, {}> {
public render() {
const {
handleTest
} = this.props;
return (
<React.Fragment>
{this.props.children}
<button onClick={handleTest(42)} label="Test2" />
</React.Fragment>
);
}
}
The interesting thing about this case is that in case of Test2 button, it seems not to recognize the argument passed to it while it logs an object -
Object { dispatchConfig: {…}, _targetInst: {…}, nativeEvent: click, type: "click", target: button.sc-bwzfXH.gonlMM, currentTarget: button.sc-bwzfXH.gonlMM, eventPhase: 3, bubbles: true, cancelable: true, timeStamp: 689, … }
In case of Test1 everything works correctly. I am wondering what am I doing wrong and whether it is typescript that messes things up or some mistake of mine
This is because when you are initilizing the <Controls /> component, the function which you are sending is something like this:
() => this.handleTest
So in the button Test2 you are executing nothing because last function just return a function
So if you want to solve this:
<Controls handleTest={(number) => this.handleTest(number)}>
<Button label="Test1" clicked={() => this.handleTest(42)} />
</Controls>
Also if you do it in this way when <Controls /> is being rendered, your function is gonna be executed and not when the user click on it. To solve this you need to change it in this way:
<React.Fragment>
{this.props.children}
<button onClick={()=>handleTest(42)} label="Test2" />
</React.Fragment>
To optimize just send the function. This is function injection
<Controls handleTest={this.handleTest}>
<Button label="Test1" clicked={() => this.handleTest(42)} />
</Controls>
You're passing a function call to the handler, this would only work if you had curried the original function instead.
<button onClick={handleTest(42)} label="Test2" /> instead of
<button onClick={() => handleTest(42)} label="Test2" />
<button onClick={handleTest(42)} label="Test2" /> would work if handleTest looked like this:
public handleTest = (testNum: number) => () => {
console.log(testNum);
};
EDIT: I would actually recommend utilizing handlers the second way, when you need to infuse an outside parameter into the handler function. By declaring a handler through
{() => doSomething()}, every time you render the component that handler function will be initialized again.
Regardless, that's just a small optimization in most cases.

semantic-ui-react List onClick declaration

i'm trying to create a list of documents dynamically with semantic-ui-react. I'd like to get the document title back when the list item is clicked. According to the documentation:
https://react.semantic-ui.com/elements/list
there is an onItemClick prop for this reason, however when im using it i get a warning when it's rendered:
Warning: Failed prop type: Prop onItemClick in List conflicts with props: children. They cannot be defined together, choose one or the other.
Also clicking on the list item does nothing (atm i just want to log the doc title to the console). Here is the code:
handleListItemClick(event, data) {
console.log("list item clicked: " + data.value);
}
buildResultsContainer() {
return this.props.listOfResults.map((document,index) =>
{
return (
<List.Item
as='a'
key={index}>
<Icon name='file' />
<List.Content>
<List.Header>{document.properties.title}</List.Header>
<List.Description>
{document.properties.description}
</List.Description>
</List.Content>
</List.Item>
);
}
);
}
render() {
return (
<div>
<List onItemClick={this.handleListItemClick}>
{this.buildResultsContainer()}
</List>
</div>
)
}
Can you please tell me how to use properly the onItemClick prop for the List component?
Less important, do you have any tip how to refactor the list rendering? Just wanted to keep the render function short and clean, but this function call looks a bit overkill....
Thanks a lot!
I think maybe the intent when using onItemClick is that you would use the items prop on List since then you wouldn't have any children e.g.
render() {
const items = this.props.listOfResults.map(document => {
return {
icon: 'file',
content: document.properties.title,
description: document.properties.description,
onClick: e => console.log(document.title)
}
});
return <List items={items} />
}
If you had your listOfResults prop in the above format, you wouldn't even need to do this map and your render function would be super tight:
render() {
return <List items={this.props.listOfResults} />;
}
Alternately, List.Item takes an onClick prop that you could define in your buildResultsContainer() function. Because each onClick function is unique based on the current document object, you will need to use an anonymous function to call your handleClick function as follows:
<List.Item
onClick={() => this.handleClick(document.title)}
...etc
/>
You would then have:
handleClick = docTitle => {
console.log(docTitle);
};
If what you wanted was obtainable from event.target, you could just pass the reference of handleClick to the onClick i.e.
handleClick = event => {
console.log(e.target.innerText);
};
<List.Item
onClick={this.handleClick}
/>

Resources