title: React child updates change parent scroll position
body: <p>I have a React container component that is suppose to house a list of child timer components. These child components are just countdown timers. </p>
  
  <p>To achieve this my child component class uses <code>setInterval</code> to update the child every second. As the updates happened, I noticed as I scrolled down or up the list, the container component would suddenly jump up or down in a noticeably big way exactly when the child timers update themselves, but the container's render method is never called. </p>
  
  <p>Along with this, if I simply don't scroll or stop scrolling, the jumping never happens. If I scrolled and then stopped scrolling, the <code>onscroll</code> handler keeps firing in sync with the child timer updates even though I stopped scrolling. </p>
  
  <p>I've never encountered a situation like this before. Can React children implicitly force their parent container to randomly scroll up and down when updating?</p>
  
  <p>Here is the code for the container:</p>
  
  <pre><code>class UserContentDetail extends React.Component {
      constructor(props) {
          super(props);
          this.state ={
              page: 1,
              perPage: 15,
              animes: [],
              currTab: props.currTab
          };
  
          this.startApiRequests = this.startApiRequests.bind(this);
          this.handleResponse = this.handleResponse.bind(this);
          this.handleData = this.handleData.bind(this);
          this.handleError = this.handleError.bind(this);
      }
  
      componentDidMount() {
          this.startApiRequests();
      }
  
      componentDidUpdate() {
          if (this.props.currTab !== this.state.currTab) {
              this.startApiRequests();
          }
      }
  
      startApiRequests() {
          let currSeason = anilistApiConstants.SEASON_SUMMER;
          let currSeasonYear = 2018;
  
          let resultPromise = null;
          switch(this.props.currTab) {
              case sideNavConstants.SIDE_NAV_TAB_MY_ANIME:
                  resultPromise = api.getMyAnimes(this.props.myAnimeIds, this.state.page, this.state.perPage);
                  break;
              case sideNavConstants.SIDE_NAV_TAB_POPULAR_ANIME:
                  resultPromise = api.getPopularAnimes(this.state.page, this.state.perPage);
                  break;
              case sideNavConstants.SIDE_NAV_TAB_NEW_ANIME:
                  resultPromise = api.getNewAnimes(currSeason, currSeasonYear, this.state.page, this.state.perPage);
                  break;
          }
  
          resultPromise.then(this.handleResponse)
              .then(this.handleData)
              .catch(this.handleError);
      }
  
      handleResponse(response) {
          return response.json().then(function (json) {
              return response.ok ? json : Promise.reject(json);
          });
      }
  
      handleData(data) {
          let results = data.data.Page.media;
          for (let i = 0; i &lt; results.length; ++i) {
              if (results[i].nextAiringEpisode == null) {
                  results[i].nextAiringEpisode = {empty: true};
              }
          }
          this.setState({
              page: 1,
              perPage: 15,
              animes: results,
              currTab: this.props.currTab
          });
      }
  
      handleError(error) {
          alert('Error, check console');
          console.error(error);
      }
  
      render() {
          console.log('rendering list');
          return(
              &lt;div className={userMasterDetailStyles.detailWrapper}&gt;
                  &lt;div className={userMasterDetailStyles.detailList}&gt;
                      {this.state.animes.map(anime =&gt; &lt;AnimeCard {...anime} key={anime.id} /&gt;)}
                  &lt;/div&gt;
              &lt;/div&gt;
          );
      }
  }
  </code></pre>
  
  <p>Here is the code for my timers (<code>AnimeCardTime</code>) and which is surrounded by a card container (<code>AnimeCard</code>):</p>
  
  <pre><code>class AnimeCardTime extends React.Component {
      constructor(props) {
          super(props);
  
          this.state = {
              timeUntilNextEpisode: props.timeLeft,
              animeId: props.id
          };
  
          this.countdownTimer = null;
  
          this.getNextEpisodeTimeUntilString = this.getNextEpisodeTimeUntilString.bind(this);
          this.startTimer = this.startTimer.bind(this);
          this.endTimer = this.endTimer.bind(this);
      }
  
      componentDidMount() {
          this.startTimer();
      }
  
      componentWillUnmount() {
          this.endTimer();
      }
  
      static getDerivedStateFromProps(nextProps, prevState) {
          if (nextProps.id !== prevState.animeId) {
              return {
                  timeUntilNextEpisode: nextProps.timeLeft,
                  animeId: nextProps.id
              };
          }
          return null;
      }
  
      startTimer() {
          if (this.props.timeLeft != undefined) {
              this.countdownTimer = setInterval(() =&gt; {
                  this.setState({
                      timeUntilNextEpisode: this.state.timeUntilNextEpisode - 60,
                  });
              }, 1000);
          }
      }
  
      endTimer() {
          if (this.countdownTimer != null) {
              clearInterval(this.countdownTimer);
          }
      }
  
      secondsToTimeString(timeSecondsUntil) {
          timeSecondsUntil = Number(timeSecondsUntil);
          let d = Math.floor(timeSecondsUntil / (3600*24));
          let h = Math.floor(timeSecondsUntil % (3600*24) / 3600);
          let m = Math.floor(timeSecondsUntil % (3600*24) % 3600 / 60);
  
          let dDisplay = d &gt; 0 ? d + 'd ' : '';
          let hDisplay = h &gt; 0 ? h + 'hr ' : '';
          let mDisplay = m &gt; 0 ? m + 'm ' : '';
          return dDisplay + hDisplay + mDisplay; 
      }
  
      getNextEpisodeTimeUntilString() {
          if (this.props.timeLeft != undefined) {
              return 'Ep ' + this.props.nextEpisode + ' - ' + this.secondsToTimeString(this.state.timeUntilNextEpisode);
          }
          else {
              return this.props.season + ' ' + this.props.seasonYear;
          }
      }
  
      render() {
          return(&lt;h6 className={userMasterDetailStyles.cardTime}&gt;{this.getNextEpisodeTimeUntilString()}&lt;/h6&gt;);
      }
  }
  
  const AnimeCard = (props) =&gt; {
  
      let secondsToTimeString = (timeSecondsUntil) =&gt; {
          timeSecondsUntil = Number(timeSecondsUntil);
          let d = Math.floor(timeSecondsUntil / (3600*24));
          let h = Math.floor(timeSecondsUntil % (3600*24) / 3600);
          let m = Math.floor(timeSecondsUntil % (3600*24) % 3600 / 60);
  
          let dDisplay = d &gt; 0 ? d + 'd ' : '';
          let hDisplay = h &gt; 0 ? h + 'hr ' : '';
          let mDisplay = m &gt; 0 ? m + 'm ' : '';
          return dDisplay + hDisplay + mDisplay; 
      };
  
      let getNextEpisodeTimeUntilString = () =&gt; {
          if (props.status === anilistApiConstants.STATUS_RELEASING) {
              return 'Ep ' + props.nextAiringEpisode.episode + ' - ' + secondsToTimeString(props.nextAiringEpisode.timeUntilAiring);
          }
          else {
              return props.season + ' ' + props.startDate.year;
          }
      };
  
      return(
          /* &lt;h6 className={userMasterDetailStyles.cardTime}&gt;{getNextEpisodeTimeUntilString()}&lt;/h6&gt; */
          &lt;a className={userMasterDetailStyles.animeCardLinkContainer} href={props.siteUrl}&gt;
              &lt;div className={userMasterDetailStyles.animeCardContainer}&gt;
                  &lt;h6 className={userMasterDetailStyles.cardTitle}&gt;{props.title.romaji}&lt;/h6&gt;
                  &lt;AnimeCardTime timeLeft={props.nextAiringEpisode.timeUntilAiring} nextEpisode={props.nextAiringEpisode.episode} season={props.season} seasonYear={props.startDate.year} id={props.id}/&gt;
                  &lt;img className={userMasterDetailStyles.cardImage} src={props.coverImage.large}/&gt;
                  &lt;p className={userMasterDetailStyles.cardDescription}&gt;{props.description.replace(/&lt;(?:.|\n)*?&gt;/gm, '')}&lt;/p&gt;
                  &lt;p className={userMasterDetailStyles.cardGenres}&gt;{props.genres.reduce((prev, curr) =&gt; {return prev + ', ' + curr;})}&lt;/p&gt;
              &lt;/div&gt;
          &lt;/a&gt;
      );
  };
  </code></pre>