Skip to content

Instantly share code, notes, and snippets.

@BlaiseGratton
Last active June 21, 2019 23:14
Show Gist options
  • Save BlaiseGratton/cade6ab7d0696e1b5788b536d40a5680 to your computer and use it in GitHub Desktop.
Save BlaiseGratton/cade6ab7d0696e1b5788b536d40a5680 to your computer and use it in GitHub Desktop.
Implementing styled components reusable with React Native

By writing a small adapter to choose the import path (styled-components vs styled-components/native), we should be able to import web UI components into a React Native context. This is accomplished by:

  1. Writing the adapter
  2. Configuring each project (web vs native) to import the right path for styled-components

The web project can easily set up a package alias in the next.config.js. The working setup I have is this:

const path = require("path");

module.exports = {
  webpack: config => {
    // Fixes npm packages that depend on `fs` module
    config.node = {
      fs: "empty"
    };
    config.resolve = {
      alias: {
        "styled-components-path": path.join(
          __dirname,
          "./node_modules/styled-components"
        )
      }
    };

    return config;
  }
};

The only thing I added was the path requirement and the config.resolve object. Now, when webpack sees "styled-components-path", it looks up the registered full path alias.

React Native does not use webpack at all since files are build statically before runtime. It uses babel to transform syntax, so we can add package aliases there with the babel-plugin-module-resolver. I added a plugins property to the scaffolded babel.config.js file like this:

module.exports = function(api) {
  api.cache(true);
  return {
    presets: ["babel-preset-expo"],
    plugins: [
      [
        "module-resolver",
        {
          root: ["./"],
          alias: {
            "styled-components-path": "./node_modules/styled-components/native"
          }
        }
      ]
    ]
  };
};

The same mapping occurs here when babel sees "styled-components-path".

To define a reusable styled component, the API is very similar to the underlying styled-components interface. A file would look like this:

import React from "react";
import getStyledElement from "styled-elements-adapter";

const { element, platform } = getStyledElement("p"); // <-- since RN components are a smaller set, we ask it for the HTML tag

const SometimesRedBlock = element` // <-- really a reference to "styled.p"
  width: 200px;
  height: 200px;
  background-color: ${platform === "web" ? "red" : "blue"};
`;

export default SometimesRedBlock;

This component is defined using a p tag, but in a React Native context getStyledElement will return styled.Text instead.

The adapter code sits between the styled component definitions and the actual styled-components package. A rough version could look like this:

import styled from 'styled-components-path' . // <-- where the aliasing magic counts

const webToMobileMap = { // these can be added as needed
  p: 'Text',             // 
  div: 'View',
  button: 'Button'
}

const getStyledElement = tagName => {
  const isWeb = Boolean(styled[tagName]) // tries to find e.g. the 'div' method in 'styled'; if it's not there, we're in React Native land
  const elementKey = isWeb ? tagName : webToMobileMap[tagName]

  return {
    element: styled[elementKey],
    platform: isWeb ? 'web' : 'native'
  }
}

export default getStyledElement
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment