1

I've used a dynamic state to give each ListItem a separate state for the collapse to be open or not. However since I have to pass a parameter to the handleClick function, my render is going through an infinite loop.

The error that I get is

Lambdas are forbidden in JSX attributes due to their rendering performance impact

It doesn't allow me to use the Lambdas so I cannot do

<ListItem button={true} onClick={() => props.handleClick(index)}>

I was wondering if there was a way around this so I can have the handleClick for every list item with their index values and without it going through an infinite loop

App.tsx

interface IState {
error: any,
intro: any,
threads: any[],
title: any,
}

export default class App extends React.Component<{}, IState> {
    constructor (props : any) {
        super (props);

        this.state = {
            error: "",
            intro: "Welcome to RedQuick",
            threads: [],
            title: ""
        };

        this.getRedditPost = this.getRedditPost.bind(this)
        this.handleClick = this.handleClick.bind(this)
    }

    public getRedditPost = async (e : any) => {
        e.preventDefault();

        const subreddit = e.target.elements.subreddit.value;
        const redditAPI = await fetch('https://www.reddit.com/r/'+ subreddit +'.json');
        const data = await redditAPI.json();

        console.log(data);

        if (data.kind) {
            this.setState({
                error: undefined,
                intro: undefined,
                threads: data.data.children,
                title: data.data.children[0].data.subreddit.toUpperCase()
            });
        } else {
            this.setState({
                error: "Please enter a valid subreddit name",
                intro: undefined,
                threads: [],
                title: undefined
            });
        }
    }

    public handleClick = (index : any)  => {
        this.setState({ [index]: true });
    }

    public render() {
        return (
            <div>
                <Header 
                    getRedditPost={this.getRedditPost}
                />
                <p className="app__intro">{this.state.intro}</p>
                {
                    this.state.error === "" && this.state.title.length > 0 ?
                    <LinearProgress />:
                    <ThreadList 
                        error={this.state.error}
                        handleClick={this.handleClick}
                        threads={this.state.threads}
                        title={this.state.title}
                    />
                }   
            </div>
        );
    }
}

Threadlist.tsx

<div className="threadlist__subreddit_threadlist">
    <List>
        { props.threads.map((thread : any, index : any) => 
            <div key={index} className="threadlist__subreddit_thread">
                <Divider />
                <ListItem button={true} onClick={props.handleClick(index)}/* component="a" href={thread.data.url}*/ >
                    <ListItemText primary={thread.data.title} secondary={<p><b>Author: </b>{thread.data.author}</p>} />
                    {props[index] ? <ExpandLess /> : <ExpandMore />}
                </ListItem>
                <Collapse in={props[index]} timeout="auto" unmountOnExit={true}>
                    <p>POOP</p>
                </Collapse>
                <Divider />
            </div>
        ) }
    </List> 
</div>

ERROR:

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.

ocuenca
  • 38,548
  • 11
  • 89
  • 102
Hyokune
  • 217
  • 1
  • 5
  • 12
  • issue is here: `onClick={props.handleClick(index)`, assign a fucntion not value, like this: `onClick={() => props.handleClick(index)}` or use currying concept: `public handleClick = (index : any) = () => { this.setState({ [index]: true }); }` – Mayank Shukla Sep 04 '18 at 10:51
  • check this answer for more details. [Maximum update depth exceeded](https://stackoverflow.com/a/45124066/5185595) – Mayank Shukla Sep 04 '18 at 10:52
  • Check [this](https://github.com/facebook/react/issues/5591) issue and discussios. – Arup Rakshit Sep 04 '18 at 10:58
  • If I try to assign a function, it gives me an error of saying "Lambdas are forbidden in JSX attributes due to their rendering performance impact" – Hyokune Sep 04 '18 at 10:59

2 Answers2

3

Use currying concept here, like this:

public handleClick = (index : any) = () => {
    this.setState({ [index]: true });
}

And use the handleClick in the same way: onClick={props.handleClick(index)}

Check this answer for more details: What is 'Currying'?

Mayank Shukla
  • 100,735
  • 18
  • 158
  • 142
0

This issue is that onClick expects a function which can handle a parameter of MouseEvent<T> | undefined. However calling onClick={handleClick(index)} resolves to means that handleClick(index) is resolved on render and renders as onClick={undefined}.

You'd need to change your onClick handler to onClick={() => handleClick(index)) or change handleClick to handleClick = (index) => () => { ... }. }

The first option would flag the tslint jsx-no-lambda rule, in which case you'd need to go with the second option.

It might be worth considering disabling the jsx-no-lamda though, the purpose behind the rule is because of a potential performance impact as a new function will be created with each render (discussed in Why shouldn't JSX props use arrow functions or bind?). However, it can make code more difficult to reason about. Generally it's considered bad to prematurely optimize code at the expense of readability.

If you later discover that an expensive render would benefit from this optimization you're much better off going with a readable solution such as memoizing it with a library such as memobind