RoboRob
RoboRob

Reputation: 195

Why a child component's state keeps clearing?

I have multiple layers of React components for getting an embed from a music service API, including a higher-order component that hits the API to populate the embed. My problem is that my lowest-level child component won't change state. I basically want the populated embed (lowest level component) to display an album cover, which disappears after clicking it (revealing an iframe), and whose state remains stable barring any change in props higher up (by the time this component is revealed, there should be no other state changes aside from focus higher up). Here's the code:

Parent:

return (
            /*...*/
                <Embed
                    embed={this.props.attributes.embed}
                    cb={updateEmbed}
                />
            /*...*/

First child ( above):

render() {
        const {embed, className, cb} = this.props;
        const {error, errorType} = this.state;
        const WithAPIEmbed = withAPI( Embed );

        /*...*/

        return <WithAPIEmbed
            embed={embed[0]}
            className={className}
            cb={cb}
        />;
        /*...*/

withAPI:

/*...*/
        componentWillMount() {
            this.setState( {fetching: true} );
        }

        componentDidMount() {
            const {embed} = this.props;

            if ( ! embed.loaded ) {
                this.fetchData();
            } else {
                this.setState( {
                    fetching: false,
                    error: false,
                } );
            }
        }

        fetchData() {
           /*... some API stuff, which calls the callback in the top level parent (cb()) setting the embed prop when the promise resolves -- this works just fine ...*/ 
        }

        render() {
            const {embed, className} = this.props;
            const {fetching, error, errorType} = this.state;

            if ( fetching ) {
                /* Return some spinner/placeholder stuff */
            }

            if ( error ) {
                /* Return some error stuff */
            }

            return (
                <WrappedComponent
                    {...this.props}
                    embed={embed}
                />
            )
        }

And finally the last child I'm interested in:

constructor() {
        super( ...arguments );
        this.state = {
            showCover: true,
        };
    }

    render() {
        const {embed, setFocus, className} = this.props;
        const {showCover} = this.state;

        if ( showCover ) {
            return [
                <div key="cover-image" className={classnames( className )}>
                    <figure className='cover-art'>
                        <img src={embed.coverArt} alt={__( 'Embed cover image' )}/>
                        <i onClick={() => {
                            this.setState( {showCover: false,} );
                        }}>{icon}</i> // <-- Play icon referenced below.
                    </figure>
                </div>,
            ]
        }

        return [
            <div key="embed" className={className}>
                    <EmbedSandbox
                        html={iframeHtml}
                        type={embed.embedType}
                        onFocus={() => setFocus()}
                    />
            </div>,
        ];
    }

My issue is that clicking the play icon should clear the album cover and reveal the iframe embed, but even though the click is registering, the state never changes (or does and then changes back). I believe it's because a higher-level component is mounting/unmounting and reinstantiating this component with its default state. I could move this state up the tree or use something like Flux, but I really feel I shouldn't need to do that, and that there's something fundamental I'm missing here.

Upvotes: 0

Views: 92

Answers (1)

Oblosys
Oblosys

Reputation: 15106

The problem is that const WithAPIEmbed = withAPI( Embed ); is inside the render method. This creates a fresh WithAPIEmbed object on each render, which will be remounted, clearing any state below. Lifting it out of the class definition makes it stable and fixes the problem.

Upvotes: 1

Related Questions