Skip to content

Commit ae3d4af

Browse files
authored
docs: add Redux example (#291)
* Redux example * Reviews implemented
1 parent 83b28b2 commit ae3d4af

File tree

13 files changed

+364
-0
lines changed

13 files changed

+364
-0
lines changed

examples/redux/App.js

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import React from 'react';
2+
import { StyleSheet, View, StatusBar } from 'react-native';
3+
import { Provider } from 'react-redux';
4+
import configureStore from './store';
5+
import AddTodo from './components/AddTodo';
6+
import TodoList from './components/TodoList';
7+
8+
const store = configureStore();
9+
10+
export default function App() {
11+
return (
12+
<Provider store={store}>
13+
<View style={styles.container}>
14+
<StatusBar barStyle="dark-content" />
15+
<AddTodo />
16+
<TodoList />
17+
</View>
18+
</Provider>
19+
);
20+
}
21+
22+
const styles = StyleSheet.create({
23+
container: {
24+
flex: 1,
25+
paddingTop: 32,
26+
},
27+
});

examples/redux/actions/todoActions.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
export const actions = {
2+
ADD: '@ADD_TODO',
3+
REMOVE: '@REMOVE_TODO',
4+
MODIFY: '@MODIFY_TODO',
5+
CLEAR: '@CLEAR_TODO',
6+
};
7+
8+
export const addTodo = (todo) => ({
9+
type: actions.ADD,
10+
payload: todo,
11+
});
12+
13+
export const removeTodo = (id) => ({
14+
type: actions.REMOVE,
15+
payload: { id },
16+
});
17+
18+
export const modifyTodo = (todo) => ({
19+
type: actions.MODIFY,
20+
payload: todo,
21+
});
22+
23+
export const clearTodos = () => ({
24+
type: actions.CLEAR,
25+
});

examples/redux/babel.config.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
module.exports = function (api) {
2+
api.cache(true);
3+
return {
4+
presets: ['module:metro-react-native-babel-preset'],
5+
};
6+
};

examples/redux/components/AddTodo.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import React from 'react';
2+
import { Button, StyleSheet, Text, View, TextInput } from 'react-native';
3+
import { bindActionCreators } from 'redux';
4+
import { connect } from 'react-redux';
5+
import { addTodo } from '../actions/todoActions';
6+
7+
export function AddTodo(props) {
8+
const [text, setText] = React.useState('');
9+
10+
const submitForm = () => {
11+
const todo = {
12+
id: props.todoLength + 1,
13+
text,
14+
date: new Date(),
15+
};
16+
17+
props.addTodo(todo);
18+
setText('');
19+
};
20+
21+
return (
22+
<View style={styles.container}>
23+
<Text style={styles.header}>Enter a text below to add a new todo</Text>
24+
<TextInput
25+
autoFocus
26+
value={text}
27+
style={styles.input}
28+
returnKeyType="search"
29+
onSubmitEditing={submitForm}
30+
onChangeText={(t) => setText(t)}
31+
placeholder="Enter the name of the repository here"
32+
/>
33+
34+
<Button onPress={submitForm} title="Submit form" />
35+
</View>
36+
);
37+
}
38+
39+
const styles = StyleSheet.create({
40+
container: {
41+
minHeight: 156,
42+
display: 'flex',
43+
flexDirection: 'column',
44+
alignItems: 'center',
45+
justifyContent: 'center',
46+
borderBottomColor: '#EEEEEE',
47+
borderBottomWidth: 2,
48+
marginBottom: 16,
49+
padding: 16,
50+
},
51+
header: {
52+
fontSize: 20,
53+
fontWeight: 'bold',
54+
marginBottom: 8,
55+
},
56+
input: {
57+
borderColor: '#DDDDDD',
58+
borderWidth: 1,
59+
paddingVertical: 8,
60+
width: '100%',
61+
textAlign: 'center',
62+
borderRadius: 4,
63+
},
64+
});
65+
66+
const mapStateToProps = ({ todos }) => ({
67+
todoLength: todos.length,
68+
});
69+
70+
const mapDispatchToProps = (dispatch) =>
71+
bindActionCreators({ addTodo }, dispatch);
72+
73+
export default connect(mapStateToProps, mapDispatchToProps)(AddTodo);
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import React from 'react';
2+
import { Provider } from 'react-redux';
3+
import { cleanup, fireEvent, render } from 'react-native-testing-library';
4+
import configureStore from '../store';
5+
import AddTodo from './AddTodo';
6+
7+
describe('Application test', () => {
8+
afterEach(cleanup);
9+
10+
test('adds a new test when entry has been included', () => {
11+
const store = configureStore();
12+
13+
const component = (
14+
<Provider store={store}>
15+
<AddTodo />
16+
</Provider>
17+
);
18+
19+
const { getByPlaceholder, getByText } = render(component);
20+
21+
const input = getByPlaceholder(/repository/i);
22+
expect(input).toBeTruthy();
23+
24+
const textToEnter = 'This is a random element';
25+
fireEvent.changeText(input, textToEnter);
26+
fireEvent.press(getByText('Submit form'));
27+
28+
const todosState = store.getState().todos;
29+
30+
expect(todosState.length).toEqual(1);
31+
32+
expect(todosState).toEqual(
33+
expect.arrayContaining([
34+
expect.objectContaining({
35+
id: 1,
36+
text: textToEnter,
37+
date: expect.any(Date),
38+
}),
39+
])
40+
);
41+
});
42+
});

examples/redux/components/TodoElem.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import React from 'react';
2+
import { StyleSheet, Button, Text, View } from 'react-native';
3+
4+
export default function TodoElem({ todo, onDelete }) {
5+
return (
6+
<View style={styles.container}>
7+
<View style={styles.textContainer}>
8+
<Text style={styles.text}>{todo.text}</Text>
9+
<Text style={styles.date}>{new Date(todo.date).toDateString()}</Text>
10+
</View>
11+
<View style={styles.buttonContainer}>
12+
<Button onPress={() => onDelete(todo.id)} title="Delete" />
13+
</View>
14+
</View>
15+
);
16+
}
17+
18+
const styles = StyleSheet.create({
19+
container: {
20+
display: 'flex',
21+
flexDirection: 'row',
22+
alignItems: 'center',
23+
justifyContent: 'center',
24+
},
25+
});

examples/redux/components/TodoList.js

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import React from 'react';
2+
import { FlatList, Text } from 'react-native';
3+
import { connect } from 'react-redux';
4+
import { bindActionCreators } from 'redux';
5+
import { removeTodo } from '../actions/todoActions';
6+
import TodoElem from './TodoElem';
7+
8+
export function TodoList(props) {
9+
const onDeleteTodo = (id) => props.removeTodo(id);
10+
11+
return (
12+
<FlatList
13+
data={props.todos}
14+
keyExtractor={(todo) => todo.id.toString()}
15+
renderItem={({ item }) => (
16+
<TodoElem todo={item} onDelete={onDeleteTodo} />
17+
)}
18+
/>
19+
);
20+
}
21+
22+
const mapStateToProps = (state) => ({
23+
todos: state.todos,
24+
});
25+
26+
const mapDispatchToProps = (dispatch) =>
27+
bindActionCreators({ removeTodo }, dispatch);
28+
29+
export default connect(mapStateToProps, mapDispatchToProps)(TodoList);
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from 'react';
2+
import { Provider } from 'react-redux';
3+
import { cleanup, fireEvent, render } from 'react-native-testing-library';
4+
import configureStore from '../store';
5+
import TodoList from './TodoList';
6+
7+
describe('Application test', () => {
8+
afterEach(cleanup);
9+
10+
test('it should execute with a store with 4 elements', () => {
11+
const initialState = {
12+
todos: [
13+
{ id: 1, text: 'Sing something', date: new Date() },
14+
{ id: 2, text: 'Dance something', date: new Date() },
15+
{ id: 3, text: 'Sleep something', date: new Date() },
16+
{ id: 4, text: 'Sleep something', date: new Date() },
17+
],
18+
};
19+
const store = configureStore(initialState);
20+
21+
const component = (
22+
<Provider store={store}>
23+
<TodoList />
24+
</Provider>
25+
);
26+
27+
const { getAllByText } = render(component);
28+
const todoElems = getAllByText(/something/i);
29+
30+
expect(todoElems.length).toEqual(4);
31+
});
32+
33+
test('should execute with 2 elements and end up with 1 after delete', () => {
34+
const initialState = {
35+
todos: [
36+
{ id: 1, text: 'Sing something', date: new Date() },
37+
{ id: 2, text: 'Dance something', date: new Date() },
38+
],
39+
};
40+
const store = configureStore(initialState);
41+
42+
const component = (
43+
<Provider store={store}>
44+
<TodoList />
45+
</Provider>
46+
);
47+
48+
const { getAllByText } = render(component);
49+
const todoElems = getAllByText(/something/i);
50+
51+
expect(todoElems.length).toBe(2);
52+
53+
const buttons = getAllByText('Delete');
54+
expect(buttons.length).toBe(2);
55+
56+
fireEvent.press(buttons[0]);
57+
expect(getAllByText('Delete').length).toBe(1);
58+
});
59+
});

examples/redux/index.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { registerRootComponent } from 'expo';
2+
3+
import App from './App';
4+
5+
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
6+
// It also ensures that whether you load the app in the Expo client or in a native build,
7+
// the environment is set up appropriately
8+
registerRootComponent(App);

examples/redux/package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "redux-example",
3+
"description": "Testing Redux interactions with RNTL",
4+
"version": "0.0.1",
5+
"scripts": {
6+
"test": "jest"
7+
},
8+
"dependencies": {
9+
"react": "~16.9.0",
10+
"react-native": "~0.61.5",
11+
"react-redux": "^7.2.0",
12+
"redux": "^4.0.5"
13+
},
14+
"devDependencies": {
15+
"@babel/core": "^7.8.6",
16+
"babel-jest": "~25.2.6",
17+
"jest": "~25.2.6",
18+
"metro-react-native-babel-preset": "^0.59.0",
19+
"react-native-testing-library": "^1.13.2",
20+
"react-test-renderer": "~16.9.0"
21+
},
22+
"private": true,
23+
"jest": {
24+
"preset": "react-native"
25+
}
26+
}

examples/redux/reducers/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
import { combineReducers } from 'redux';
2+
import todos from './todoReducer';
3+
4+
export default combineReducers({
5+
todos,
6+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { actions } from '../actions/todoActions.js';
2+
3+
export default function todoReducer(state = [], action) {
4+
switch (action.type) {
5+
case actions.ADD:
6+
return state.concat(action.payload);
7+
8+
case actions.REMOVE:
9+
console.log(action);
10+
return state.filter((todo) => todo.id !== action.payload.id);
11+
12+
case actions.MODIFY:
13+
return state.map((todo) => {
14+
if (todo.id === action.payload.id) {
15+
return {
16+
...todo,
17+
...action.payload,
18+
};
19+
}
20+
});
21+
22+
case actions.CLEAR:
23+
return [];
24+
25+
default:
26+
return state;
27+
}
28+
}

examples/redux/store.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { createStore } from 'redux';
2+
import reducers from './reducers';
3+
4+
const initialStore = {
5+
todos: [],
6+
};
7+
8+
export default function configureStore(initialState = initialStore) {
9+
return createStore(reducers, initialState);
10+
}

0 commit comments

Comments
 (0)