GluePear
GluePear

Reputation: 7715

Using setInterval in React delays component render

I have a parent component, <App>:

constructor() {
    super();            
    this.state = {
        transporterPos: 0
    }
    this.tick = this.tick.bind(this);
}

componentDidMount() {
    this.timerId = setInterval(() => this.tick(), 1000);
}

componentWillUnmount() {
    clearInterval(this.timerId);
}

tick() {
    let transporterPos = this.state.transporterPos;
    transporterPos++;
    if (transporterPos > 7) {
        transporterPos = 0;
    }
    this.setState({ transporterPos: transporterPos });
}

render() {
    return (
        <div>
            <Staves transporterPos={this.state.transporterPos}/>
        </div>
    );
}

The <Staves> component contains several <Stave> components, each of which contains several <Note> components. Each <Note> component is injected with a className conditional on its active property being true:

<div className="noteContainer" onClick={this.handleClick}>
    <div className={"note" + (this.props.active ? ' active' : '')}></div>
</div>

handleClick() is a method that toggles a <Note>'s active property. I'm not including all the code here to make this more readable. The problem is that when clicking on a <Note>, although its active property changes immediately, the styling given by the conditional className of 'active' is not visible until the component is re-rendered at the next "tick" of the setInterval method. In other words, rendering only seems to happen once every 1000ms. I would like it to happen immediately. Am I using setInterval wrong?

Edit:

In response to comments, here is the handleClick method (in <Note>):

handleClick() {
    this.props.toggleActive(this.props.pos);
}

This calls toggleActive in <Stave>:

toggleActive(pos) {
    this.props.notes[pos].active = !this.props.notes[pos].active;
}

props.notes here is part of <App>'s state, which is passed down to <Stave> (and which I didn't include in this question for the sake of brevity).

Upvotes: 2

Views: 667

Answers (1)

FuzzyTree
FuzzyTree

Reputation: 32392

toggleActive(pos) {
    this.props.notes[pos].active = !this.props.notes[pos].active;
}

The reason a re-render isn't being triggered is because this.props is mutated directly instead of with setState. Move toggleActive further up to where you can use setState.

If necessary you can pass the function as a prop to the child component and call it via this.props.toggleActive()

Besides not triggering a re-render, another reason this.props should never be mutated directly is because your changes will get overwritten whenever the parent changes state and passes props to its children.

Upvotes: 1

Related Questions