Let's try to draw a full interface. Rewrite App.js using the components from the native-base library:
import React from 'react'; import {Container, Content} from 'native-base'; import {StyleSheet, Text, View} from 'react-native'; import AppFooter from './components/AppFooter.js'; const styles = StyleSheet.create({ container: { padding: 20 }, }); const App = () => ( <Container> <Content> <View style={styles.container}> <Text> Lorem ipsum... </Text> </View> </Content> <AppFooter/> </Container> ); export default App;
We see a new component AppFooter, which we have to create. Go to the folder ./components/ and create the file AppFooter.js with the following contents:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; const AppFooter = () => ( <Footer> <FooterTab> <Button active> <Text></Text> </Button> <Button> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Everything is ready to try to build our application!
Our buttons do not know how to switch. It's time to teach them. To do this, you need to do two things: learn how to handle a click event and learn how to store a state. Let's start with the state. Since we refused to store the state in the component, making a choice in favor of the pure components and the global store, we will use Redux.
First of all, we have to create our story.
import {createStore} from 'redux'; const initialState = {}; const store = createStore(reducers, initialState);
Let's create a billet for reducer. In the reducers folder, create an index.js file with the following contents:
export default (state = [], action) => { switch (action.type) { default: return state } };
We connect reduser to App.js:
import reducers from './reducers';
Now we need to extend our stor on components. This is done using the Provider components specifically. We connect it to the project:
import {Provider} from 'react-redux';
And wrap all the components in Provider. The updated App.js looks like this:
import React from 'react'; import {Container, Content} from 'native-base'; import {StyleSheet, Text, View} from 'react-native'; import AppFooter from './components/AppFooter.js'; import {createStore} from 'redux'; import {Provider} from 'react-redux'; import reducers from './reducers'; const initialState = {}; const store = createStore(reducers, initialState); const styles = StyleSheet.create({ container: { padding: 20 }, }); const App = () => ( <Provider store={store}> <Container> <Content> <View style={styles.container}> <Text> Lorem ipsum... </Text> </View> </Content> <AppFooter/> </Container> </Provider> ); export default App;
Now our application can store its state. Let's take advantage of this. Add the state mode, the default setting in ARTICLES. This means that during the first render, our application will be set to display the list of articles.
const initialState = { mode: 'ARTICLES' };
Not bad, but manually typing string values leads to potential errors. Let's get constants. Create a ./constants/index.js file with the following contents:
export const MODES = { ARTICLES: 'ARTICLES', PODCAST: 'PODCAST' };
And rewrite App.js:
import {MODES} from './constants'; const initialState = { mode: MODES.ARTICLES };
Great, the state is there, it's time to transfer it to the footer component. Let's look at our ./components/AppFooter.js again:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; const AppFooter = () => ( <Footer> <FooterTab> <Button active> <Text></Text> </Button> <Button> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
As we can see, the state of the switch is determined using the active property of the Button component. Prokin to Button current state of the application. This is done not difficult, the Provider component, which we connected earlier, takes on the main engine compartment. It remains only to take from it the current state and put AppFooter components into the props. First of all, we modify our AppFooter so that the state of the buttons can be controlled by passing mode via props:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; import {MODES} from "../constants"; const AppFooter = ({mode = MODES.ARTICLES}) => ( <Footer> <FooterTab> <Button active={mode === MODES.ARTICLES}> <Text></Text> </Button> <Button active={mode === MODES.PODCAST}> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Now let's start creating the container. Create a ./containers/AppFooterContainer.js file.
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {MODES} from "../constants"; const AppFooterContainer = () => ( <AppFooter mode={MODES.ARTICLES} /> ); export default AppFooterContainer;
And we connect the AppFooterContainer container in App.js instead of the AppFooter component. While our container is no different from the components, but everything will change as soon as we connect it to the state of the application. Let's do it!
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {connect} from 'react-redux'; const mapStateToProps = (state) => ({ mode: state.mode }); const AppFooterContainer = ({mode}) => ( <AppFooter mode={mode} /> ); export default connect( mapStateToProps )(AppFooterContainer);
Very functional! All features have become clean. What's going on here? We connect our container to the state using the connect function and connect its props with the contents of the global state using the mapStateToProps function. Very clean and beautiful.
So, we learned to spread data from top to bottom. Now we need to learn how to change our global state from the bottom up. To generate events about the need to change the global state are actions. Let's create an action that occurs when a button is clicked.
Create a ./actions/index.js file:
import { SET_MODE } from './actionTypes'; export const setMode = (mode) => ({type: SET_MODE, mode});
And the file ./actions/actionTypes, in which we will store constants with action names:
export const SET_MODE = 'SET_MODE';
The action creates an object with the name of the event and the data set that accompanies this event, and nothing more. Now learn how to generate this event. We return to the AppFooterContainer container and connect the mapDispatchToProps function which connects the event dispatchers to the container's props.
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {connect} from 'react-redux'; import {setMode} from '../actions'; const mapStateToProps = (state) => ({ mode: state.mode }); const mapDispatchToProps = (dispatch) => ({ setMode(mode) { dispatch(setMode(mode)); } }); const AppFooterContainer = ({mode, setMode}) => ( <AppFooter mode={mode} setMode={setMode} /> ); export default connect( mapStateToProps, mapDispatchToProps )(AppFooterContainer);
Great, we have a function that spawns the SET_MODE event and we get it up to the AppFooter component. There are two problems left:
Nobody calls this function
No one is listening to the event.
Let's deal with the first problem. Go to the AppFooter component and enable the function call setMode.
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; import {MODES} from "../constants"; const AppFooter = ({mode = MODES.ARTICLES, setMode = () => {}}) => ( <Footer> <FooterTab> <Button active={mode === MODES.ARTICLES} onPress={ () => setMode(MODES.ARTICLES)}> <Text></Text> </Button> <Button active={mode === MODES.PODCAST} onPress={ () => setMode(MODES.PODCAST)}> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Now, clicking the button will trigger the SET_MODE event. It remains to learn how to change the global state as it arises. Go to the previously created ./reducers/index.js and create a reducer for this event:
import { SET_MODE } from '../actions/actionTypes'; export default (state = [], action) => { switch (action.type) { case SET_MODE: { return Object.assign({}, state, { mode: action.mode }); } default: return state } };
Gorgeous! Now click on the button generates an event that changes the global state, and the footer, having received these changes, redraws the buttons.