Search code examples
buttonreact-nativestyled-components

Dynamically Styled Button in React Native using Styled Components


A Button component is generally comprised of the Text element wrapped with a TouchableHighlight (or other touchable). I'm trying to create a Button component styled using styled-components, but am having trouble getting my style to respond dynamically to props.

Button Component

Below, I've created a Button component similar to the Adapting based on props example found in the styled-component docs.

import React from 'react';
import { Text, TouchableHighlight } from 'react-native';
import styled from 'styled-components/native';

const colors = {
    accent: '#911',
    highlight: '#D22',
    contrast: '#FFF',
}

const Label = styled.Text`
  color: ${props => !props.outline ? colors.contrast : colors.accent};
  font-weight: 700;
  align-self: center;
  padding: 10px;
`

const ButtonContainer = styled.TouchableHighlight`
  background-color: ${props => props.outline ? colors.contrast : colors.accent};
  width: 80%;
  margin-top: 5px;
  border-color: ${colors.accent};
  border-width: 2px;
`

const Button = (props) => {
    return (
        <ButtonContainer
            onPress={props.onPress}
            underlayColor={colors.highlight}
        >
            <Label>
                {props.children}
            </Label>
        </ButtonContainer>
    );
};

export default Button;

Button Usage

After importing it, I'm using the button like this...

    <Button
      outline
      onPress={() => console.log('pressed')}>
      Press Me!
    </Button>

Expected Result

And so, I would expect my button to look like this...

enter image description here

Actual Result

But instead it looks like this... enter image description here

What I've done to troubleshoot so far

When I inspect using react-devtools, I can see that the outline prop is being passed down to the Button component.

enter image description here

But the prop is not passed down to any of it's children

enter image description here

The Passed Props part of the docs state, "styled-components pass on all their props", but I guess not all the way down?

My Question

What do I need to change so that I can dynamically style my Button based on it's props?


Solution

  • Here you have:

    const Button = (props) => {
        return (
            <ButtonContainer underlayColor={colors.highlight}>
                <Label>
                    {props.children}
                </Label>
            </ButtonContainer>
        );
    };
    

    If ButtonContainer was a normal React component, you wouldn't expect the props passed to Button to be automatically passed to ButtonContainer. You'll have to do <ButtonContainer underlayColor={colors.highlight} {...props} /> to do it.

    Actually ButtonContainer is a normal React component, the only difference is you pre-apply some styles using an HOC.

    Also if you desugar this to a React.createElement call, you can see there's no way props can be passed automatically, because a Function's arguments don't get passed automatically to the function calls inside it.

    const Button = (props) => {
        return React.createElement(ButtonContainer, { underlayColor: colors.highlight }, ...);
    };
    

    It's nothing specific to styled-components. You just have to pass down the props yourself to ButtonContainer, as well as to Label.

    So you'd rewrite your code to:

    const Button = (props) => {
        return (
            <ButtonContainer underlayColor={colors.highlight} onPress={props.onPress} outline={props.outline}>
                <Label outline={props.outline}>
                    {props.children}
                </Label>
            </ButtonContainer>
        );
    };
    

    Technically a React component can pass down props to it's children, so ButtonContainer could pass them down to Label using React.Children and React.cloneElement APIs. But ButtonContainer doesn't do that for obvious reasons, e.g. you'd not want underlayColor and onPress to be passed to Label automatically. It would cause a lot of confusing bugs.