90

I'm trying to load a splash screen for an iOS app built in React Native. I'm trying to accomplish this through class states and then a setTimeout function as follows:

class CowtanApp extends Component {
  constructor(props){
    super(props);
    this.state = {
      timePassed: false
    };
  }

  render() {
    setTimeout(function(){this.setState({timePassed: true})}, 1000);
    if (!this.state.timePassed){
      return <LoadingPage/>;
    }else{
      return (
        <NavigatorIOS
          style = {styles.container}
          initialRoute = {{
            component: LoginPage,
            title: 'Sign In',
          }}/>
      );
    }
  }
}

The loading page works for a second, and then I guess when setTimeout tries to change the state to true, my program crashes: 'undefined is not an object (evaluating this.setState)'. I've been going at this for a couple of hours, any ideas on how to fix it?

Phil
  • 1,614
  • 3
  • 16
  • 17
  • Check that your device's time matches that of your computer! This happened to me and took an unfortunately long while to `debug` https://stackoverflow.com/questions/51163349/javascript-settimeout-immediately-runs-in-react-native – Mahdi Bashirpour Jul 03 '18 at 22:26
  • For some reason all I can see is that `setTimeout` is used inside the `render` method and this will create a new time out for each render... Creating side effects inside `render` is an antipattern. The rest is already answered – kidroca Apr 20 '21 at 16:36

11 Answers11

169

Classic javascript mistake.

setTimeout(function(){this.setState({timePassed: true})}, 1000)

When setTimeout runs this.setState, this is no longer CowtanApp, but window. If you define the function with the => notation, es6 will auto-bind this.

setTimeout(() => {this.setState({timePassed: true})}, 1000)

Alternatively, you could use a let that = this; at the top of your render, then switch your references to use the local variable.

render() {
  let that = this;
  setTimeout(function(){that.setState({timePassed: true})}, 1000);

If not working, use bind.

setTimeout(
  function() {
      this.setState({timePassed: true});
  }
  .bind(this),
  1000
);
Community
  • 1
  • 1
oliversisson
  • 2,229
  • 1
  • 14
  • 14
  • 2
    Saved me, thank you! I'm new to js, this may be silly but is there a way to do it with the "traditional" function(){} way? – t-gao Dec 08 '16 at 17:56
  • Please mention that usually you would not call `setTimeout` inside the `render` method. It's a React antipattern to modify state or make side effects inside render. Also a new timer is created for each render pass – kidroca Apr 20 '21 at 16:38
16

Write a new function for settimeout. Pls try this.

class CowtanApp extends Component {
  constructor(props){
  super(props);
  this.state = {
  timePassed: false
  };
}

componentDidMount() {
  this.setTimeout( () => {
     this.setTimePassed();
  },1000);
}

setTimePassed() {
   this.setState({timePassed: true});
}


render() {

if (!this.state.timePassed){
  return <LoadingPage/>;
}else{
  return (
    <NavigatorIOS
      style = {styles.container}
      initialRoute = {{
        component: LoginPage,
        title: 'Sign In',
      }}/>
  );
}
}
}
Phyo
  • 692
  • 1
  • 5
  • 14
  • okay, this works - thank you! could you possibly explain why it wasn't working in render? – Phil Dec 29 '15 at 04:49
  • I think you can't write any instructions at all in render method. You can use componentWillMount or componentDidMount function for startup instructions. – Phyo Dec 29 '15 at 05:02
  • 6
    It was not working because of a scoping issue. In your original code you had setTineout(function() { which when in that block this refers to something other then your component. An alternative to the answer here would have been to simply change your setTimeout call to the "ES2015 Fat Arrow Sytax" like: setTimeout(() => this.setState((...) – rmevans9 Dec 29 '15 at 10:02
11
const getData = () => {
// some functionality
}

const that = this;
   setTimeout(() => {
   // write your functions    
   that.getData()
},6000);

Simple, Settimout function get triggered after 6000 milliseonds

krishnazden
  • 1,127
  • 10
  • 19
8

In case anyone wants it, you can also make the timer async and await it:

export const delay = (ms) => new Promise((res) => setTimeout(res, ms));

Usage:

// do something
await delay(500); // wait 0.5 seconds
// do something else
Eric Wiener
  • 4,929
  • 4
  • 31
  • 40
7

Change this code:

setTimeout(function(){this.setState({timePassed: true})}, 1000);

to the following:

setTimeout(()=>{this.setState({timePassed: true})}, 1000); 
Harry
  • 678
  • 10
  • 26
wandhi Zakari
  • 75
  • 1
  • 2
7

On ReactNative .53, the following works for me:

 this.timeoutCheck = setTimeout(() => {
   this.setTimePassed();
   }, 400);

'setTimeout' is the ReactNative library function.
'this.timeoutCheck' is my variable to hold the time out object.
'this.setTimePassed' is my function to invoke at the time out.

david m lee
  • 2,547
  • 26
  • 14
5

You can bind this to your function by adding .bind(this) directly to the end of your function definition. You would rewrite your code block as:

setTimeout(function () {
  this.setState({ timePassed: true });
}.bind(this), 1000);
Scott Carpenter
  • 363
  • 4
  • 4
2

Never call setState inside render method

You should never ever call setState inside the render method. Why? calling setState eventually fires the render method again. That means you are calling setState (mentioned in your render block) in a loop that would never end. The correct way to do that is by using componentDidMount hook in React, like so:

class CowtanApp extends Component {
  state = {
     timePassed: false
  }

  componentDidMount () {
     setTimeout(() => this.setState({timePassed: true}), 1000)
  }

  render () {
    return this.state.timePassed ? (
        <NavigatorIOS
          style = {styles.container}
          initialRoute = {{
            component: LoginPage,
            title: 'Sign In',
        }}/>
    ) : <LoadingPage/>
  }
}

PS Use ternary operators for cleaner, shorter and readable code.

UtkarshPramodGupta
  • 7,486
  • 7
  • 30
  • 54
2
import React, {Component} from 'react';
import {StyleSheet, View, Text} from 'react-native';

class App extends Component {
  componentDidMount() {
    setTimeout(() => {
      this.props.navigation.replace('LoginScreen');
    }, 2000);
  }

  render() {
    return (
      <View style={styles.MainView}>
        <Text>React Native</Text>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  MainView: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
});

export default App;
1

There looks to be an issue when the time of the phone/emulator is different to the one of the server (where react-native packager is running). In my case there was a 1 minute difference between the time of the phone and the computer. After synchronizing them (didn't do anything fancy, the phone was set on manual time, and I just set it to use the network(sim) provided time), everything worked fine. This github issue helped me find the problem.

im-a-teapot
  • 398
  • 4
  • 12
0

Same as above, might help some people.

setTimeout(() => {
  if (pushToken!=null && deviceId!=null) {
    console.log("pushToken & OS ");
    this.setState({ pushToken: pushToken});
    this.setState({ deviceId: deviceId });
    console.log("pushToken & OS "+pushToken+"\n"+deviceId);
  }
}, 1000);
Dunken_sai
  • 343
  • 3
  • 7