Search code examples
reactjsreact-reduxdom-manipulationvirtual-dom

Trying to manipulate a div with reactjs on async data


I try to animate a div with reactjs using async data via redux and it's not clear to me when can I have a reference to the virtual dom on state loaded.

In my case I have a div with id header where I would like to push down the container when data was populated.

If I try in componentDidMount than I get Cannot read property 'style' of undefined because componentDidMount still having a reference to an on load container

class HomePage extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            sliderLength: null
        }
    }

    componentDidMount() {
        this.props.actions.getSlides()


        if(this.header) {

            setTimeout(function() {
                this.header.style.bottom = -(this.header.clientHeight - 40) + 'px';
            }, 2000);
        }
            //header.style.bottom = -pushBottom+'px';

    }


    componentWillReceiveProps(nextProps) {
        let {loaded} = nextProps
        if(loaded === true ) {
            this.animateHeader()
        }
    }

    animateHeader() {

    }

    componentWillMount() {
        const {slides} = this.props;
        this.setState({
            sliderLength: slides.length,
            slides: slides
        });
    }


    render() {
        const {slides, post, loaded} = this.props;

        if(loaded ===true ) {

            let sliderTeaser = _.map(slides, function (slide) {
                if(slide.status === 'publish') {
                    return  <Link  key={slide.id}  to={'portfolio/' + slide.slug}><img key={slide.id} className="Img__Teaser" src={slide.featured_image_url.full} /></Link>
                }
            });

            let about = _.map(post, function (data) {
                return data.content.rendered;
            })

            return (
                <div className="homePage">
                    <Slider columns={1}  autoplay={true} post={post} slides={slides} />

                    <div id="header" ref={ (header) => this.header = header}>
                        <div className="title">Title</div>
                        <div className="text-content">
                            <div dangerouslySetInnerHTML={createMarkup(about)}/>
                        </div>

                        <div className="sliderTeaser">
                            {sliderTeaser}
                        </div>

                        <div className="columns">
                            <div className="column"></div>
                            <div className="column"></div>
                            <div className="column"></div>
                        </div>

                    </div>

                    <div id="bgHover"></div>
                </div>
            );
        } else {
            return <div>...Loading</div>
        }

    }
}

function mapStateToProps(state) {
    return {
        slides: state.slides,
        post: state.post,
        loaded: state.loaded
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: bindActionCreators(slidesActions, dispatch)
    };
}

function createMarkup(markup) {
    return {__html: markup};
}

export default connect(mapStateToProps, mapDispatchToProps)(HomePage);

How do I deal in this case with states?

Between I found a solution but not sure if is the right workaround

componentDidUpdate() {
        if(this.header) {
            setTimeout(function() {
                this.header.style.bottom = -(this.header.clientHeight - 35) + 'px';
            }, 2000);
        }
    }

Solution

  • In general, try to avoid using ref as much as possible. This is particularly difficult if you're new to React but with some training, you'll find yourself not needing it.

    The problem with modifying the styles like you're doing is that when the component will render again your changes will be overwritten.

    I would create a new state property, say state.isHeaderOpen. In your render method you will render the header differently depending on the value of this header e.g.:

    render () {
      const {isHeaderOpen} = this.state
      return (
        <header style={{bottom: isHeaderOpen ? 0 : 'calc(100% - 40px)'}}>
      )
    }
    

    Here I'm using calc with percentage values to get the full height of the header.

    Next, in your componentDidMount simply update the state:

    componentDidMount () {
      setTimeout(() => this.setState({isHeaderOpen: false}), 2000);
    }
    

    In this way, the component will render again but with the updated style.

    Another way is to check if the data has been loaded instead of creating a new state value. For example, say you're loading a list of users, in render you would write const isHeaderOpen = this.state.users != null.