Code Sharing Between React Web And React Native

November 21, 2018

One of the advantages of using React Native is the possibility of sharing code across different devices. This is achievable because under the hood React Native is using the same component which is used on the web.

The difference is only in platform-specific UI components. This is why we will reuse the logic of the app. For applying styles I will use styled components which have React Native implementation, this will help with reusing styles across different platforms.

The starting point is root component which is the same for all platforms:

import React, { Fragment } from 'react';
import ListUsers from './containers/ListUsers';
import CreateUser from './containers/CreateUser';

const App = () => (
  <Fragment>
    <ListUsers />
    <CreateUser />
  </Fragment>
)

export default App;

This component includes 2 containers in which resides our shared logic:

import React, { Component } from 'react';
import API from '../services/api'
import List from '../components/List';

export default class ListUsers extends Component {
  state = {
    persons: []
  }

  componentDidMount() {
    API.get('users')
      .then(({ data: persons }) => this.setState({ persons }));
  }

  render() {
    const { persons } = this.state;
    return <List data={persons} />;
  }
}
import React, { Component, Fragment } from 'react';
import { pathOr } from 'ramda';
import API from '../services/api'
import Input from '../components/Input';
import Button from '../components/Button';

export default class CreateUser extends Component {
  state = {
    name: '',
  }

  handleChange = event => {
    const name = pathOr(event, ['target', 'value'])(event);
    this.setState({ name });
  }

  handleSubmit = () => {
    const { name: user } = this.state;

    API.post('users', { user })
      .then(res => console.log(res.data));
  }

  render() {
    const { name } = this.state;
    return (
      <Fragment>
        <Input value={name} handleChange={this.handleChange} />
        <Button onAction={this.handleSubmit} title="Create user" />
      </Fragment>
    )
  }
}

All the magic is happening when we are importing components:

import List from '../components/List';

List is a folder with different extensions .js.ios.js and .android.js. Depending on which platform we want to execute our app, the corresponding file will be loaded. .js will be loaded for the web version, the rest will be loaded according to platform extension, more details here.

And, lastly we will reuse styles, by creating a common styles file:

import { css } from 'styled-components';

const styles = css`
 background-color: red;
`
export default styles;

Then we will wrap platform specific element/ component.

For web:

import React from 'react';
import styled from 'styled-components';
import styles from './styles';

const Button = styled.button`${styles}`;

const ButtonItem = ({ title, onAction }) => (
  <Button onClick={onAction}>
    {title}
  </Button>
)

export default ButtonItem;

And for mobile:

import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';
import styles from './styles';

const StyledView = styled.View`${styles}`;

const ButtonItem = ({ onAction, title = 'Button' }) => (
  <StyledView>
    <Button
      onPress={onAction}
      title={title}
      accessibilityLabel={title}
    />
  </StyledView>
);

export default ButtonItem;

Here is the repo with all code.