1

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???

HoldOffHunger
  • 18,769
  • 10
  • 104
  • 133
xunux
  • 1,531
  • 5
  • 20
  • 33
  • Why are you storing the random values in the state in the first place? Is there are situation you do not want it to change? – Felix Kling Sep 27 '18 at 00:02
  • hmm no im used to, in other cases, save it in state from within my function so i can render the state and that way when it changes it will render the change. Thats usually for fetch calls though, so it may not be necessary in this case. – xunux Sep 27 '18 at 00:09
  • are you using react-router? Maybe you could look into `withRouter` and `componentDidUpdate` – bozdoz Sep 27 '18 at 01:06
  • hey @bozdoz no im not using react-router or any routing in this particular app since it's very simple and has only 3 sections that are basically working like tabs – xunux Sep 27 '18 at 01:23
  • 1
    @xunux just saying: the component will need to know when you want something to change. Maybe look into react context api? I wouldn't think you should use redux. if the component is a child of the tabs, you could pass a unique prop to alter it... just trying to help you think of a way to achieve this. Good luck. – bozdoz Sep 27 '18 at 02:05
  • @bozdoz thanks, i appreciate it. It turns out though that the problem is not in the lifecycle method, my original approach was fine because the component IS getting mounted and unmounted between tabs. The real problem turns out is in my randomizing function, that seems to be modifying my data destructively and upon a second call of the function it returns undefined. I might need to make that a new question! But i do appreciate the feedback and suggestions for getting that to work if the componen was always mounted! – xunux Sep 27 '18 at 02:19

2 Answers2

2

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>
    );
  };
}
Matt Carlotta
  • 18,972
  • 4
  • 39
  • 51
1

If you want to have the numbers randomized on each render call, add that logic into your render function:

render() {
  const randomized = this.randomizeSponsors(); // IMPORTANT: CHANGE THIS TO MAKE IT RETURN ARRAY INSTEAD OF SETSTATE
  return (
    <div>
      {randomized}
    </div>
  );
}
izb
  • 562
  • 4
  • 11
  • this makes sense and I had tried it, but for some reason it doesnt work. If i makethe function randomizeSponsors() console.log the result befor returning it, the console shows the randomized array in the console, and then it re-prints an empty array. This implies the function is getting called twice and for some reason the second time around the array returns empty – xunux Sep 27 '18 at 01:21