I have infinite slideshow on my project, I'm not sure if my methods are in right order. I'm calling fetch function on componentWillMount()
and then using that data on componentDidMount()
..
The problem could be something else but it worked before, But now it doesn't..
componentWillMount() {
this.props.getAdverts();
}
componentDidMount() {
var index = 0;
setInterval(() => {
this.setState({
bg: this.props.adverts ? this.props.adverts[index++].photo: this.state.bg,
text: this.props.adverts ? this.props.adverts[index++].text: this.state.text
})
if(index === this.props.adverts.length) index = 0;
}, 4000)
}
When I log the this.props.adverts
, it's array..
The error is: Cannot read property 'photo' of undefined
or Cannot read property 'text' of undefined
STACKBLITZ link: https://stackblitz.com/edit/react-sshop
This is an example of how you might want to do this using your current code. I disagree with the way this is being done, but for starters this should help you to start exploring a more React-ish way of coding.
// This is a composable function, we will pass it in to setInterval.
// we need access to the component, so we will pass it in and then
// return the function signature that setInterval wants to call
const changeAdvertComposer = function changeAdvertComposer(component) {
// we start at -1 so the first call asks for 0
let index = -1;
// return the function that setInterval will call
return function changeAdvert() {
const adverts = component.props.adverts;
if (!adverts || !adverts.length) {
// we have no advertisements so lets exit
return;
}
index++;
// reset our index if needed
if (index >= adverts.length) {
index = 0;
}
// increment and grab our object
const advert = adverts[index];
// grab our state or a default failover structure
const state = component.state.advert || { bg: '', text: '' };
// set our state
component.setState({
advert: {
bg: advert.photo || state.bg,
text: advert.text || state.text,
}
});
}
};
export ExampleAdvertManager extends Component {
// setup some validations on our props
static propTypes = {
getAdverts: PropTypes.func.isRequired,
adverts: PropTypes.arrayOf(PropTypes.shape({
photo: PropTypes.string,
text: PropTypes.string
}))
}
constructor(props) {
super(props);
// we will store the state in the interval object so we can
// check for null (simple way to find out if loading)
this.state = {
advert: null
};
// we will store the ref to our interval here
this._intervalRef = null;
}
componentDidMount() {
// we are loaded let's call our data action loader
this.props.getAdverts();
}
componentWillUpdate() {
// do we have any ads?
const adlength = this.props.adverts ? this.props.adverts.length : 0;
if (adlength && !this._intervalRef) {
// we have ads and we have not setup the interval so lets do it
this._intervalRef = setInterval(changeAdvertComposer(this), 4000);
} else if (!adlength && this._intervalRef) {
// we have no ads but we have an interval so lets stop it
clearInterval(this._intervalRef);
this._intervalRef = null;
}
}
componentWillUnmount() {
// we are unloading, lets clear up the interval so we don't leak
if (this._intervalRef) {
clearInterval(this._intervalRef);
this._intervalRef = null;
}
}
render() {
if (this.stage.advert) {
// render here
}
// we don't have data yet
return null; // or some loading view
}
}
And I may have gone overboard in this example, I've been doing this too long and really depend on composability for unit testing. It makes it hard for me to not think in that manner. I did not make setState composable though... the rabbit hole goes much deeper.
Really I would have made the interval timer a component of its own that renders null and fires callbacks to my component. Just makes everything cleaner. That would look something like this:
class TimerComponent extends PureComponent {
static propTypes = {
onInterval: PropTypes.func.isRequired,
interval: PropTypes.number.isRequired,
immediate: PropTypes.bool,
}
static defaultProps = {
immediate: true,
}
componentDidMount() {
this._intervalRef = setInterval(this.props.onInterval, this.props.interval);
if (this.props.immediate) {
this.props.onInterval();
}
}
componentWillUnmount() {
clearInterval(this._intervalRef);
}
render() {
return null;
}
}
// We will still use the composable function, but in a differnt way.
// The function stays the same
const changeAdvertComposer = function changeAdvertComposer(component) {
// we start at -1 so the first call asks for 0
let index = -1;
// return the function that setInterval will call
return function changeAdvert() {
const adverts = component.props.adverts;
if (!adverts || !adverts.length) {
// we have no advertisements so lets exit
return;
}
index++;
// reset our index if needed
if (index >= adverts.length) {
index = 0;
}
// increment and grab our object
const advert = adverts[index];
// grab our state or a default failover structure
const state = component.state.advert || { bg: '', text: '' };
// set our state
component.setState({
advert: {
bg: advert.photo || state.bg,
text: advert.text || state.text,
}
});
}
};
export ExampleAdvertManager extends Component {
// setup some validations on our props
static propTypes = {
getAdverts: PropTypes.func.isRequired,
adverts: PropTypes.arrayOf(PropTypes.shape({
photo: PropTypes.string,
text: PropTypes.string
}))
}
constructor(props) {
super(props);
// we will store the state in the interval object so we can
// check for null (simple way to find out if loading)
this.state = {
advert: null
};
}
componentDidMount() {
// we are loaded let's call our data action loader
this.props.getAdverts();
}
render() {
if (this.stage.advert) {
return (
<div>
<TimerComponent interval={4000} onInterval={changeAdvertComposer(this)} />
{
// render what you want to do from state
}
</div>
);
} else if (this.props.adverts) {
return (
<TimerComponent key="interval" interval={4000} onInterval={changeAdvertComposer(this)} />
);
}
// we don't have data yet
return null; // or some loading view
}
}
I hope this helps.