Blog

This past year we started working with React-Native for the first time at Osedea. We’ve had our fair share of learning and wanted to share some key findings with you.

To begin with, you should have knowledge of the concepts of Presentational and Container components (see here if not).

Personally, I prefer calling the former "Components", and the latter "Views", with my Views being connected to the redux state and my Components being "Dumb"

In this post, I will only talk about what we learned about writing good React-Native Presentational components, not Containers.

TL;DR

  • Imports > Component Class > Styles
  • As soon as you copy-paste the part of a render function, create a Component (There's a Component for that ©)
  • Only put what influences the rendering of your component in the state
  • Always do your propTypes homework
  • Create your propTypes using the propTypes of the children Components and the base React PropTypes
  • Make your Components extra customizable with lots of simple *Style properties (e.g. ContainerStyle)

Note

All extracts of code are written in ES6 code because we love it, and if you are not doing it yet, I strongly advise you to install and use a linter in your project. It saves so much time and effort, with the added benefit of having a nice, clean and coherent code in your team!

If you like our coding style, you can use our linter config : eslint-config-osedea. And if you want to save some time and you use atom as your editor, you should definitely check out our snippets package.

Base structure

What should my Component look like?

In the community, you can see the following guidelines:

  • Put some doc if needed
  • Put your imports first (external imports first, then relative/project imports)
  • Define your Component Class in which:
    • Define your propTypes
    • Define your defaultProps
    • Lifecycle methods in the order they would happen (constructor, componentWillMount, componentWillReceiveProps, componentWillUnmount, etc.)
    • Your helper methods/handlers (Personally, I like to put all my PressHandlers together, then all my other methods)
    • Partial render functions if your render() gets too big
    • render() function
  • Define your styles

Example on a Pokemon Class:

/**
 *
 *
 * @class Pokemon
 * @classdesc The Pokemon Component displays a Pokemon in a nature Scene
 * @extends React.Component
 *
 *
 * @author Adrien Thiery <adrien.thiery@osedea.com>
 * @version 0.1.0
 *
 * @copyright Osedea
 *
 */

import React, { Component } from 'react'; // RN 0.25+
import {
    StyleSheet,
    View,
} from 'react-native';
import { pokemonTypes } from 'pokemongodb.net';

import PokemonFace from './PokemonParts/PokemonFace';
import PokemonBody from './PokemonParts/PokemonBody';
import PokemonArms from './PokemonParts/PokemonArms';
import PokemonFeet from './PokemonParts/PokemonFeet';
import PokemonInformationDiplay from './PokemonInformationDiplay';

export default class Pokemon extends Component {
    static propTypes = { ... };

    static defaultProps = { ... };

    constructor(props) { ... }

    componentWillReceiveProps(nextProps) { ... }

    handleFacePress = () => { ... };
    handleBodyPress = () => { ... };
    handleArmsPress = () => { ... };
    handleFeetPress = () => { ... };

    renderFace = () => { ... };
    renderBody = () => { ... };
    renderArms = () => { ... };
    renderFeet = () => { ... };

    render() {
        return (
            <View
                style={[
                    styles.scene,
                    this.props.sceneStyle,
                ]}
            >
                {this.state.isAlive
                    ? <View
                        style={[
                            styles.pokemonContainer,
                            this.props.pokemonContainerStyle,
                        ]}
                    >
                        {this.renderFace()}
                        {this.renderBody()}
                        {this.renderArms()}
                        {this.renderFeet()}
                    </View>
                    : null
                }
                <PokemonInformationDiplay
                    combatPoints={this.props.combatPoints}
                    experiencePoints={this.props.experiencePoints}
                    name={this.props.name}
                    style={this.props.pokemonInformationDiplayStyle}
                    type={this.props.type}
                />
            </View>
        );
    }
}

const colors = {
    green: '#00EE55',
};

const styles = StyleSheet.create({ ... });

When should I create a Component?

My answer is straightforward: as soon as you copy-paste some code from the render function of one Component into another because it shows the same thing, you should have a Component for that. If you copy-paste to modify it big time, this doesn't apply, of course.

Components can and should be very simple and straightforward. For example, in my Pokemon example, my PokemonFace Component will be extremely simple:

/**
 *
 *
 * @class PokemonFace
 * @classdesc The PokemonFace Component displays the face of a Pokemon
 * @extends React.Component
 *
 *
 * @author Adrien Thiery <adrien.thiery@osedea.com>
 * @version 0.1.0
 *
 * @copyright Osedea
 *
 */

import React, { Component } from 'react'; // RN 0.25+
import {
    Image,
    StyleSheet,
} from 'react-native';
import { getFaceImage } from 'MyProject/managers/ImageManager';
                            // ^ RN knows where the root of my project is ;)

export default class PokemonFace extends Component {
    static propTypes = {
        style: Image.propTypes.style,
        name: React.PropTypes.string.isRequired,
    };

    render() {
        return (
            <Image
                source={getFaceImage(this.props.name)}
                style={[
                    styles.faceImage,
                    this.props.style,
                ]}
            />
        );
    }
}

const styles = StyleSheet.create({
    faceImage: {
        resizeMode: 'contain',
    },
});

Don't hesitate to create a lot of Components!

Keep it light

Try to keep the state of your Component as empty as possible. Only put in the things that you know are going to change. Otherwise, use the this.props directly in your render().

PropTypes

PropTypes are AWESOME!

A Component will tell you if there is an issue with a type of data based on the propTypes they receive. In addition, propTypes are almost the only documentation that you need for a Component.

PropTypes are highly reusable and composable. You don't only define the propTypes for your Component but also for the components that are going to use it.

For example, in my Pokemon Component propTypes, I will use some React.PropTypes, some View.propTypes and some of my Components' propTypes (PokemonArms.propTypes, PokemonBody.propTypes, etc.):

export default class Pokemon extends Component {
    static propTypes = {
        attacks: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                damage: React.PropTypes.number.isRequired,
                isLongAttack: React.PropTypes.bool.isRequired,
                name: React.PropTypes.string.isRequired,
                type: React.PropTypes.oneOf(pokemonTypes).isRequired,
            })
        ),
        combatPoints: React.PropTypes.number.isRequired,
        evolutions: React.PropTypes.arrayOf(
            React.PropTypes.instanceOf(Pokemon)
        ),
        experiencePoints: React.PropTypes.number.isRequired,
        hitPoints: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        pokemonArmsStyle: PokemonArms.propTypes.style,
        pokemonBodyStyle: PokemonBody.propTypes.style,
        pokemonContainerStyle: View.propTypes.style,
        pokemonFaceStyle: PokemonFace.propTypes.style,
        pokemonFeetStyle: PokemonFeet.propTypes.style,
        pokemonInformationDiplayStyle: PokemonInformationDiplay.propTypes.style,
        sceneStyle: View.propTypes.style,
        type: React.PropTypes.oneOf(pokemonTypes).isRequired,
    };

    ...
}

Each time I need to use a Component that I've never seen before, the FIRST thing I do is look at the propTypes, because they will tell me exactly the range of customization that I will be able to do to it.

Know your styles

When you create a Component, it is important to make it as reusable as possible. Styling can be a mess if your component isn't done properly. Here are some tips to make things smoother:

  • Always try to make your styles 'overridable' by using a prop after your style

    Example:

    renderFace = () => (
         <PokemonFace
             name={this.props.name}
             onPress={this.handleFacePress}
             style={[
                 styles.pokemonFace,
                 this.props.pokemonFaceStyle,
             ]}
         />
    );

    The great thing with that is that you don't have to worry about the pokemonFaceStyle property being undefined, React-Native will discard it if it is undefined, null or {}.

    This also allows you to create "Wrapper" Components.

    In several of our projects, we have one basic Button Component and 3 or 4 other *Button Components whose only role are to pass specific styles.

    For example, I could have a PokemonDirtyFace Component that uses PokemonFace and just adds a backgroundColor to my PokemonFace.

    Ideally, all the Components used in your render() could be styled with a style property.

  • DO NOT pass style Objects as properties as soon as you have more than one thing to style.

    We made this mistake at first, defining a pokemonStyle object in the parent and passing it to the Pokemon Component:

    // DO NOT DO THAT
    const pokemonStyle = {
        face: { ... },
        body: { ... },
        arms: { ... },
        feet: { ... },
    };
    
    render() {
         return (
             <Pokemon
                    name={'squirtle'}
                    style={pokemonStyle}
             />
         );
    }

    This gets very messy, very quickly, and you lose some of the best features of the StyleSheet object.

    It is better to do separate styles as shown in the Pokemon Component.

    // DO THAT
    const pokemonStyles = StyleSheet.create({
        face: { ... },
        body: { ... },
        arms: { ... },
        feet: { ... },
    });
    
    render() {
         return (
             <Pokemon
                 name={'squirtle'}
                 pokemonFaceStyle={pokemonStyles.face}
                 pokemonBodyStyle={pokemonStyles.body}
                 pokemonArmsStyle={pokemonStyles.arms}
                 pokemonFeetStyle={pokemonStyles.feet}
             />
         );
    }

Conclusion

Thank you for reading, I hope you found this article useful.

We are still learning every day (I learned things writing this article). Please give us feedback if there is an error, a typo, something you do not agree with or something you figured out how to do better than us, we crave to get better everyday and we'd love to chat!

If you liked this article, check out some of our Components!






Bonus: The complete Pokemon Component as I imagined it while writing this article.

/**
 *
 *
 * @class Pokemon
 * @classdesc The Pokemon Component displays a Pokemon in a nature Scene
 * @extends React.Component
 *
 *
 * @author Adrien Thiery <adrien.thiery@osedea.com>
 * @version 0.1.0
 *
 * @copyright Osedea
 *
 */

import React, { Component } from 'react'; // RN 0.25+
import {
    StyleSheet,
    View,
} from 'react-native';
import { pokemonTypes } from 'pokemongodb.net';

import PokemonFace from './PokemonParts/PokemonFace';
import PokemonBody from './PokemonParts/PokemonBody';
import PokemonArms from './PokemonParts/PokemonArms';
import PokemonFeet from './PokemonParts/PokemonFeet';
import { jiggle } from './PokemonAnimations';
import PokemonInformationDiplay from './PokemonInformationDiplay';

export default class Pokemon extends Component {
    static propTypes = {
        attacks: React.PropTypes.arrayOf(
            React.PropTypes.shape({
                damage: React.PropTypes.number.isRequired,
                isLongAttack: React.PropTypes.bool.isRequired,
                name: React.PropTypes.string.isRequired,
                type: React.PropTypes.oneOf(pokemonTypes).isRequired,
            })
        ),
        combatPoints: React.PropTypes.number.isRequired,
        evolutions: React.PropTypes.arrayOf(
            React.PropTypes.instanceOf(Pokemon)
        ),
        experiencePoints: React.PropTypes.number.isRequired,
        hitPoints: React.PropTypes.number.isRequired,
        name: React.PropTypes.string.isRequired,
        pokemonArmsStyle: PokemonArms.propTypes.style,
        pokemonBodyStyle: PokemonBody.propTypes.style,
        pokemonContainerStyle: View.propTypes.style,
        pokemonFaceStyle: PokemonFace.propTypes.style,
        pokemonFeetStyle: PokemonFeet.propTypes.style,
        pokemonInformationDiplayStyle: PokemonInformationDiplay.propTypes.style,
        sceneStyle: View.propTypes.style,
        type: React.PropTypes.oneOf(pokemonTypes).isRequired,
    };

    static defaultProps = {};

    constructor(props) {
        super(props);

        this.state = {
            hitPoints: props.hitPoints,
            isAlive: true,
        };
    }

    componentWillReceiveProps(nextProps) {
        if (nextProps.hitPoints !== this.props.hitPoints) {
            this.setState({
                hitPoints: nextProps.hitPoints,
                isAlive: nextProps.hitPoints > 0,
            });
        }
    }

    handleFacePress = () => {
        jiggle('face');
    };
    handleBodyPress = () => {
        jiggle('body');
    };
    handleArmsPress = () => {
        jiggle('arms');
    };
    handleFeetPress = () => {
        jiggle('feet');
    };

    renderFace = () => (
        <PokemonFace
            name={this.props.name}
            onPress={this.handleFacePress}
            style={[
                styles.pokemonFace,
                this.props.pokemonFaceStyle,
            ]}
        />
    );
    renderBody = () => (
        <PokemonBody
            name={this.props.name}
            onPress={this.handleBodyPress}
            style={[
                styles.pokemonBody,
                this.props.pokemonBodyStyle,
            ]}
        />
    );
    renderArms = () => (
        <PokemonArms
            name={this.props.name}
            onPress={this.handleArmsPress}
            style={[
                styles.pokemonArms,
                this.props.pokemonArmsStyle,
            ]}
        />
    );
    renderFeet = () => (
        <PokemonFeet
            name={this.props.name}
            onPress={this.handleFeetPress}
            style={[
                styles.pokemonFeet,
                this.props.pokemonFeetStyle,
            ]}
        />
    );

    render() {
        return (
            <View
                style={[
                    styles.scene,
                    this.props.sceneStyle,
                ]}
            >
                {this.state.isAlive
                    ? <View
                        style={[
                            styles.pokemonContainer,
                            this.props.pokemonContainerStyle,
                        ]}
                    >
                        {this.renderFace()}
                        {this.renderBody()}
                        {this.renderArms()}
                        {this.renderFeet()}
                    </View>
                    : null
                }
                <PokemonInformationDiplay
                    style={this.props.pokemonInformationDiplayStyle}
                    combatPoints={this.props.combatPoints}
                    experiencePoints={this.props.experiencePoints}
                    name={this.props.name}
                    type={this.props.type}
                />
            </View>
        );
    }
}

const colors = {
    green: '#00EE55',
};

const styles = StyleSheet.create({
    scene: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: colors.green,
    },
    pokemonContainer: {
        width: 200,
        height: 500,
    },
});
Adrien Thiery