Calling a function each time a component is rendered, since componentDidMount() only triggers once

xunux picture xunux · Sep 27, 2018 · Viewed 7.5k times · Source

EDIT: Upon further testing i realized that componentDidMount IS behaving how i was expecting, but it seems to be a problem with my randomizing function that for some reason returns undefined if it gets run a second time, so on initial mount it gives me results, when i mount it again it returns undefined and hence nothing renders...

I have done research to understand why I am having this issue. I have a component for a webapp, which is just a banner that will render Sponsor names. Everytime this banner gets rendered I want to randomize the order in which sponsors appear. I can get this to work, but it is only happening the first time it gets rendered (on componentDidMount()) and then every time i visit any section where that component gets rendered it no longer calls componentDidMount() so it renders no content.

At first i thought componentDidMount() should be called everytime i enter a section that is calling that component. After reading around, everywhere said that the component is being mounted once and that is why it is only calling the function once. How can I make it so it randomizes everytime I render the component?

Here is sample of the code I'm writing.

class SponsorBanner extends React.Component {
    constructor(props){
        super(props)
        this.state = {
            randomized: []
        }

        this.randomizeSponsors = this.randomizeSponsors.bind(this)
    }

    randomizeSponsors(){
        let copy = []
        const array = ['a','b','c','d']
        let n = array.length
        let i;

        while(n){
            i = Math.floor(Math.random() * n--)
            copy.push(array.splice(i,1)[0])
        }

        let randomizedArray = copy.map((sponsor,i) => <div key={i}>{sponsor}</div>)

        this.setState({randomized:randomizedArray})

    }
    componentDidMount(){
        this.randomizeSponsors()
    }


    render(){

        return (
            <div>
                {this.state.randomized}
            </div>
        )
    }
}

This component gets rendered in 2 different sections of the app. It is a very simple app and I am not even adding routing to it. It just works entirely as a SPA. When a user click a section, it renders that component. On first load if I visit a section that renders this component it displays a randomized array. If I navigate to another section and back, it renders an empty banner.

Why is this happening and how could I work around it???

Answer

Matt Carlotta picture Matt Carlotta · Sep 27, 2018

Another approach could be to use asetIterval if you want it to change every x amount of milliseconds.

Working example: https://codesandbox.io/s/k9zz9wq9lo

import random from "lodash/random";
import React, { Component } from "react";

export default class Banner extends Component {
  state = {
    randomIndex: 0
  };

  componentDidMount = () => this.setTimer();

  componentWillUnmount = () => this.clearTimer();

  clearTimer = () => clearInterval(this.timeout);

  timer = () => this.setState({ randomIndex: random(0, 999) });

  setTimer = () => (this.timeout = setInterval(this.timer, 3000));

  render = () => (
    <img
      src={`https://loremflickr.com/600/100?lock=${this.state.randomIndex}`}
    />
  );
}

Or, if you don't want to use a setIterval and this component is always mounted... and you only want it to change when state changes... then have a HOC component manage state. You can then utilize the HOC's render method to update a variable.

Working Example: https://codesandbox.io/s/6qvl49vqw

import random from "lodash/random";
import React, { Component } from "react";
import Sample from "./Sample";

export default class Parent extends Component {
  state = {
    loadC1: false
  };

  shouldComponentUpdate = (nextProps, nextState) =>
    this.state.loadC1 !== nextState.loadC1;

  handleClick = () =>
    this.setState(prevState => ({ loadC1: !this.state.loadC1 }));

  render = () => {
    const randomIndex = random(0, 999);
    const { loadC1 } = this.state;

    return (
      <div style={{ textAlign: "center" }}>
        <button
          className="uk-button uk-button-primary"
          onClick={this.handleClick}
        >
          {!loadC1 ? "Show" : "Hide"} Component
        </button>
        <div style={{ marginBottom: 40 }}>
          {loadC1 && <Sample />}
        </div>
        <img src={`https://loremflickr.com/600/100?lock=${randomIndex}`} />
      </div>
    );
  };
}