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: