coulix
coulix

Reputation: 3368

React native component life cycle: `ref` usage and `null` object

I have a parent component index.js

  render() {
    const { firstName, token } = this.props.user;
  
    if (token && firstName) {
      return (
        <View style={{ flex: 1 }}>
          <HomeRoot />
        </View>
      );
    }

    console.log('=== ELSE');
    return (
      <View style={{ flex: 1 }}>
        <SplashScreen />
      </View>
    );
  }
}

And a SplashScreen that shows while the user is not logged in:

// Methods imports.
import React from 'react';
import { View, Text, Image, TouchableOpacity, StyleSheet } from 'react-native';
import { connect } from 'react-redux';
import { Asset, AppLoading, Font, DangerZone } from 'expo';

import FadeInView from '../animations/FadeInView';

// Redux actions
import { signinUser } from '../../store/actions/actions';

const { Lottie } = DangerZone;

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

function cacheImages(images) {
  return images.map(image => {
    if (typeof image === 'string') {
      return Image.prefetch(image);
    }

    return Asset.fromModule(image).downloadAsync();
  });
}

function cacheFonts(fonts) {
  return fonts.map(font => Font.loadAsync(font));
}

class SplashScreen extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      isReady: false
    };
    this.bgAnim = null;
  }

  setBgAnim(anim) {
    // if (anim === null) {
    //   return;
    // }
    this.bgAnim = anim;
    this.bgAnim.play();
  }

  async loadAssetsAsync() {
    const imageAssets = cacheImages([
     // ...
    ]);

    const fontAssets = cacheFonts([{
      'cabin-bold': CabinBold,
      'league-spartan-bold': LeagueSpartanBold
    }]);

    await Promise.all([...imageAssets, ...fontAssets]);
  }

  render() {
    if (!this.state.isReady) {
      return (
        <AppLoading
          startAsync={this.loadAssetsAsync}
          onFinish={() => this.setState({ isReady: true })}
        />
      );
    }

    return (
      <View style={styles.wrapper}>
        <Lottie
          ref={c => this.setBgAnim(c)}
          resizeMode="cover"
          style={{
            position: 'absolute',
            zIndex: 1,
            left: 0,
            top: 0,
            width: '100%',
            height: '100%',
          }}
          source={require('../../../assets/SPLASH_03.json')}  // eslint-disable-line
        />
      </View>
    );t
  }
}

export default connect(
  null,
  { signinUser }
)(SplashScreen);

The signinUser calls facebookAuth and then store the fetched user profile and token in one unique dispatch.

At this point the index.js

token && firstName are true and the SplashScreen component should let his place to HomeRoot.

However it crashes on SplashScreen render method: ref={c => this.setBgAnim(c)}. If we remove this line, or check to discard c when c is null everything works as expected.

Why is c null at this stage in ref={c => this.setBgAnim(c)}?

How should I handle this problem in a better way than checking for null?

enter image description here

Upvotes: 2

Views: 458

Answers (1)

Tomasz Mularczyk
Tomasz Mularczyk

Reputation: 36199

From docs:

React will call the ref callback with the DOM element when the component mounts, and call it with null when it unmounts. ref callbacks are invoked before componentDidMount or componentDidUpdate lifecycle hooks.

Knowing that at some points ref passed to callback will be a null, just do a check:

setBgAnim(anim) {
  this.bgAnim = anim;
  if(anim) {
    this.bgAnim.play();
  }
}

I don't think there is something wrong with such approach.

Upvotes: 4

Related Questions