Themeable react native (Part 2) — Changing themes


In Part One, we created a theme with base themes and various color themes. We were able to serve the theme through redux and use styled components to apply the theme.

Here, we are going to use redux to change the theme of the app to bring the app full circle.

We will make changes to the login page to allow the user to select the theme they prefer. We will add two Pickers, one for the base theme and another for the color options.

Actions

We first set up two action creators. The first receives a base theme object and sends a dispatch of type “CHANGE BASE THEME”. The second receives a color theme object and also fires a dispatch of type “CHANGE COLOR THEME”

Reducer

The reducer discards the current theme object in the initial state and combines the theme object in the action payload with the parts of the theme that do not change

Login

Now we need to tweak the login page to allow the user to change the theme.

We import the theme objects from the store and the two action creators as well. Using mapDispatchToProps, we can access the functions from the Login Component.

We now add two selection inputs and populate them with the base theme and color theme options respectively. When the user taps any option, we trigger a function that invokes the relevant action creator.

export const changeBaseTheme = BaseTheme => {
  //dispatch an action to change light or dark theme
  return dispatch => {
    dispatch({
      type: "CHANGE_BASE_THEME",
      baseTheme: BaseTheme
    });
  };
};

export const changeColorTheme = ColorTheme => {
  //dispatch an action to change accent color theme theme
  return dispatch => {
    dispatch({
      type: "CHANGE_COLOR_THEME",
      colorTheme: ColorTheme
    });
  };
};
import * as React from "react";
import { Picker } from "react-native";
import { lightTheme, darkTheme, colorOptions } from "./store/theme";
import { connect } from "react-redux";
import styled, { ThemeProvider } from "styled-components";
import { bindActionCreators } from "redux";
import { changeBaseTheme, changeColorTheme } from "./store/actions";

const Container = styled.View`
  flex: 1;
  flex-direction: column;
  justify-content: space-between;
  background-color: ${props => props.theme.PRIMARY_BACKGROUND_COLOR};
`;

const Header = styled.View`
  padding-top: 10;
  padding-bottom: 10;
  padding-left: 10;
  padding-right: 10;
  background-color: ${props => props.theme.PRIMARY_COLOR};
`;

const HeaderText = styled.Text`
  font-size: 24;
  color: ${props => props.theme.PRIMARY_FOREGROUND_COLOR};
  font-family: ${props => props.theme.PRIMARY_FONT_FAMILY};
`;

const Body = styled.View`
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
  background-color: ${props => props.theme.PRIMARY_BACKGROUND_COLOR};
  padding-top: 30;
  padding-bottom: 30;
  padding-left: 30;
  padding-right: 30;
`;

const Segment = styled.View`
  padding-top: 10;
  padding-bottom: 10;
  flex-direction: column;
  justify-content: space-between;
  align-items: stretch;
`;

const Icon = styled.Image`
  height: 60;
  width: 60;
`;
const Title = styled.Text`
  color: ${props => props.theme.PRIMARY_TEXT_COLOR};
  font-size: ${props => props.theme.FONT_SIZE_MASSIVE};
  font-family: ${props => props.theme.PRIMARY_FONT_FAMILY};
`;

const Description = styled.Text`
  color: ${props => props.theme.PRIMARY_TEXT_COLOR};
  font-size: ${props => props.theme.FONT_SIZE_MEDIUM};
  font-family: ${props => props.theme.PRIMARY_FONT_FAMILY};
  padding-top: 20;
`;

const ItemPicker = styled.Picker`
  color: ${props => props.theme.PRIMARY_TEXT_COLOR};
  padding-top: 20;
`;

const Footer = styled.View`
  padding-top: 20;
  padding-bottom: 20;
  padding-left: 20;
  padding-right: 20;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  background-color: ${props => props.theme.PRIMARY_BACKGROUND_COLOR};
`;

const Button = styled.TouchableOpacity`
  padding-top: 10;
  padding-bottom: 10;
  padding-left: 10;
  padding-right: 10;
  flex-direction: column;
  justify-content: center;
  align-items: stretch;
  elevation: 1
  border-radius: 2;
  
  background-color:${props => props.theme.PRIMARY_COLOR};
`;

const ButtonText = styled.Text`
  text-align: center;
  color: ${props => props.theme.PRIMARY_FOREGROUND_COLOR};
  font-family: ${props => props.theme.PRIMARY_FONT_FAMILY};
  font-size: ${props => props.theme.FONT_SIZE_LARGE};
`;

class Login extends React.Component {
  render() {
    return (
      <ThemeProvider theme={this.props.theme}>
        <Container>
          <Header>
            <HeaderText>Login</HeaderText>
          </Header>
          <Body>
            <Segment>
              <Icon
                source={{
                  uri: "https://img.icons8.com/dusk/50/000000/lock-2.png"
                }}
              />
            </Segment>

            <Segment>
              <Title>Login</Title>
              <Description>
                Please enter your username and password to continue
              </Description>
            </Segment>

            <Segment>
              <ItemPicker
                onValueChange={(itemValue, itemIndex) =>
                  //make redux action to change the light or dark theme
                  itemValue !== 0 && this.props.changeBaseTheme(itemValue)
                }
                selectedValue={null}
              >
                <Picker.Item label="Please select an base theme" value="0" />
                <Picker.Item label="Dark" value={darkTheme} />
                <Picker.Item label="Light" value={lightTheme} />
              </ItemPicker>
            </Segment>
            <Segment>
              <ItemPicker
                style={{}}
                onValueChange={(itemValue, itemIndex) =>
                  //make redux action to change the accent color theme
                  this.props.changeColorTheme(itemValue)
                }
              >
                <Picker.Item label="Please select an color theme" value="0" />
                {Object.keys(colorOptions).map((option, i) => (
                  //create options for each color option in our theme.js file
                  <Picker.Item
                    key={i}
                    label={option}
                    value={colorOptions[option]}
                  />
                ))}
              </ItemPicker>
            </Segment>
          </Body>

          <Footer>
            <Button>
              <ButtonText>Login</ButtonText>
            </Button>
          </Footer>
        </Container>
      </ThemeProvider>
    );
  }
}

const mapStateToProps = state => ({
  theme: state.themeReducer.theme
});

const mapDispatchToProps = dispatch => ({
  changeBaseTheme: bindActionCreators(changeBaseTheme, dispatch),
  changeColorTheme: bindActionCreators(changeColorTheme, dispatch)
});

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Login);
import { base, darkTheme, lightTheme, colorOptions } from "./theme";
// blue
const initialState = {
  theme: { ...base, ...lightTheme, ...colorOptions.blue }
};

const themeReducer = (state = initialState, action) => {
  switch (action.type) {
    case "CHANGE_BASE_THEME":
      let newState = {
        ...state,
        theme: { ...state.theme, ...action.baseTheme }
      };
      return newState;
    case "CHANGE_COLOR_THEME":
      let newStateTheme = {
        ...state,
        theme: { ...state.theme, ...action.colorTheme }
      };
      return newStateTheme;
    default:
      return state;
  }
};

export default themeReducer;

Conclusion

Users love products that they can make their own. Small customizations such as colour can go a really long way


The repository can be found here:

Good stuff? Consider sharing it!
Default image
Evans Munene
I pour milk before my cereal!