I would just like to share this implementation of the Todo App with React Native and Redux that is patterned after the folder structure in the Todo App in the Basic Tutorial of the React Redux Documentation so I decided to make this a beginners guide to Redux article as well to explain the app I shared below.
Here is the expo link:
https://snack.expo.io/@roycechua/redux-react-native-sample-simple-todo
Feel free to try it out first, then if you need the explanations about the app and Redux then here is my take on it below. If you want more formal explanations by the way please refer to the documentations below or visit other more detailed explanations in the dev.to community.
- Redux Documentation (https://redux.js.org/)
This is not directly related to React/React Native because Redux can be used with other Frameworks as well.
- React-Redux Documentation (https://react-redux.js.org/)
This contains the functions and component we need to use Redux with React/React Native.
What can the app do?
This Todo App simply allows you to add and remove tasks as shown in the snack.expo.io. I did this because I found it hard to actually study the React Todo App because of how much stuff is on there. So I made this app from scratch as well as the article to help beginners, or developers in general including myself who want to learn about Redux and how it works on React Native.
Redux?
So if you don't know what's Redux, you can just think of it like a collection of functions with a component called the Provider that allows you to access and change your state from any component within the app.
Redux is a State Management Library for projects that have so many state variables that needs to be accessed from multiple components as well as for many other reasons you can read about in their official websites. Redux mostly removes the need to pass state values as props around components (in some instances components that have components that have components.. so on) just to get other state values like your App Settings.
The Important Parts
The 5 important parts for anyone starting out in learning about Redux should focus on understanding are these:
1. Store
Store is like a literal storage area, A plastic container with Dividers. You can either retrieve or update what's inside of those little boxes inside (Don't mind what's inside).
The store is the global app state container and its made accessible by the Provider component from react-redux
<Provider>
/* The Rest of your App's Components */
</Provider>
That's why in the App.js file which is my most top-level component in the App. The code looks like this
Notice how the store is given as a prop, the value of the prop which I conveniently named store as well but... What is being passed to the store prop?
Simply put a store is just(but not really just) a Function that returns an Object or Array but most of the time an object.
You can change up the code in the snack to be like this
<Provider store={() => {}} />
<TodoApp/>
</Provider>
So know we know what the store requires, a function that returns an object.
So what's the object? It's your global state.
But remember the major rule, you cannot directly modify or make changes to that object like you can do with a normal Javascript Object because we want to make its contents easy to determine or determinable (if there's such a word).
That fat arrow function we created that to return an empty object is actually what we'll use to make changes to the state. Enter the reducer but for now will go back to the snack.
Now that we know this let's explore what's on the snack. If you would notice the import store from './redux/store'
when you open the file you see this
What's createStore()? The createStore function basically adds some additional functions like .dispatch() that will allow you to send an action to the reducer but it still returns your state.
Inside the createStore is the reducer which is what we'll be talking about next.
2. Reducer
The reducer is just a function that returns an object either your entire state if you're only using 1 Reducer like in our example or multiple pieces of the global state (Like the boxes above inside the 1 big container).
What does this function take in as arguments? The reducer function takes in two things: A State, and an Action. An action is just a Javascript Object with two keys
{ type: 'ADD_TODO', payload: { /* Whatever value(s) you want */ } }
But let's talk more about Actions later, let's focus on Reducers.
Here is the Todos Reducer simplified and commented (I purposely changed the code here compared to the snack so you could see a variation in the implementation.
const initialState = {
todo_list: []
};
function todos_reducer(state = initialState, action) {
switch (action.type) { // action.type is a String
case 'ADD_TODO': // The name of the action tells what it's gonna do
// Add any code you need here
/*
This is essentially a new object with values copied from
state along with a new todo_list
return {
...state,
// new todo_list (don't forget to copy existing todo items)
}
*/
return {
...state,
todo_list: [ ...state.todo_list, action.payload]
}
case 'DELETE_TODO':
/*
state.todo_list.filter() will return a new array
without the elements that you specified in the condition.
*/
return {
...state,
todo_list: state.todo_list.filter(
(todo) => todo.id != action.payload.id
)
};
default:
return state
}
}
The reason the documentation calls it a pure function is because we do not and should not modify the state we return but what we could do is create a copy of the current state then either exclude(for delete) or include(add or update) our changes or additions to the state. Also, Asynchronous code are not allowed in a reducer.
The point is reducer functions either return the original state or a brand new state whose values are copied from the original state.
Going back to this where we see rootReducer
What is the rootReducer? We need to go to the actual file itself on the snack, there is another folder on redux named reducers.
The index.js file contains a single most important function which is combineReducers({ todos }).
Just to be clear rootReducer is actually combineReducers({ todos }). The reason why we hid it away is because we just want to make the code more manageable and nice to look at.
combineReducers() from the redux library simply allows you to combine multiple reducer functions which I've said holds pieces of your global state. For example, besides todos_reducer, when the app grows larger it might have a user account portion of the state that will need its own reducer.
So why did I wrap my todos reducer inside a combineReducers() function when I only had one reducer? It's to prepare my app to be scaled to accommodate multiple reducers if it ever does.
You can just think of combineReducers as one big reducer function composed of many smaller reducer functions.
Now, going to my todos reducer we're going to find that it only varies a little bit from what I already made above
Feel free to try it while your reading this part.
So what does the global state look like if we have added some todos?
{
todos: {
todos_list: [
{id:1, task: "Go for a walk"},
{id:2, task: "Take your vitamins"},
]
}
}
This is the reason why the global store should return an object, because when you eventually have more than one reducer, eventually every reducer will have a key in the global state and its value is the initialState you defined in that reducer file.
So now we know the store and reducer, we can finally shed some light on Actions which we've already seen here on Reducer.
3. Actions
Actions as I said before is nothing more than another Javascript object but it has two keys (as a standardized way only, it's not strict) which are type and payload.
From my snack example on the actions.js file here, you can see that this is exactly what it does, it returns an object.
The functions that returns the action object are called the action creators. Action creators is what we will be importing to our React Native / React Components.
Remember that the Action object (shown below) is what will tell the Reducer function what to do and the data it needs to do the intended task.
{
type: ADD_TODO,
payload: {
id: ++nextTodoId,
task
}
}
Our addTodo action creator can take in input from the user in the component when we call it later but in fact, if I didn't need parameters or inputs from the user in the addTodo action creator, I would have just simply written the addTodo action as
export const addTodo = {
type: ADD_TODO,
payload: {
id: ++nextTodoId,
task: "A JAVASCRIPT OBJECT WITH NO WAY OF GETTING USER INPUT"
}
};
So what is special about Actions is its purpose.
Again as a constant reminder, Actions dictate what code should the reducer execute assuming that it knows that action (remember the switch statement?). That's the purpose of the action.type and action.payload. The action.payload is the actual data you want to store in the state or use for updating or deleting data in the state.
But the main point about Actions is that it isn't capable of doing anything it's only a message to be sent to the reducer so that the reducer knows what to do with your state.
So then, How does the message reach the Reducer(s)?
The dispatch function that we got from createStore() remember?
4. Dispatch
If you'll visit the Redux documentation you'll see a demo of how dispatch is used in their counter app.
store.dispatch({ type: 'INCREMENT' })
This is how the Action reaches the Reducer because remember the store creation?
let store = createStore(Your_Reducer);
In short, the dispatch is a function from your created store that is used to send your action to the reducer(s) and the reducer(s) will determine what to do using their switch case comparison with action.type.
But, you may be wondering because my snack.expo app does not have this dispatch()? so where is it?
There's a little magic the react-redux library did for us to avoid manually passing the dispatch() from component to component to component... as well as other functions unless we explicitly need to and that's through the connect() higher-ordered function.
5. Connect
connect() which is imported from react-redux is the last important part of our Redux for beginners journey because this is the function that allows you to perform dispatch() in your component but also retrieve any redux state variables you want to access for your component.
I made this todoapp a one liner so that you can quickly go to it on screens/TodoApp. Observe these imports on the snack.
import { connect } from 'react-redux';
import { addTodo, deleteTodo } from '../redux/actions';
Then scroll to the bottom and find the export default code.
It looks weird but essentially the main syntax to use the connect is
export default connect()(YourComponent);
But what's the magic that allows this component to access our todo list for display? and how does this component allow adding and deleting a todo?
The magic is definitely not black magic but the connect() does a lot of things for you already so you don't have to.
connect() should be passed with two objects, the first one is the mapStateToProps object (again just a standardized name) and mapDispatchToProps object.
The mapStateToProps object if you follow my code which is actually based on the generic redux syntax. This code allows you to retrieve the redux state through the state parameter and assign a key to that redux state variable. In my case I needed to return the todos_list so I could display it to the screen.
Important: Remember that you need to retrieve them as prop objects (either destructured or as props.todos_list)
In mapDispatchToProps, you will need to add the actions to that object and they will automatically be wrapped around in the dispatch() function like this dispatch(addTodo).
But I could have also implemented it this way just for the sake of having a choice.
In my case for this app at least, this method was unnecessary. So I used the more convenient one in my snack.
Important: Remember that you still need to retrieve them as prop objects (either destructured or as props.addTodo)
Simply calling the mapped action like addTodo(), allowed me to quickly invoke the dispatch function by calling the addTodo function that returns my Action object which gets interpreted(like the example below) then sent to the rootReducer.
{
type: 'ADD_TODO',
payload: {
id: 1,
task : "Go for a walk"
}
}
There are many ways that the connect() function can be confusing as well as the mapStateToProps and mapDispatchToProps object but this is one approach I could suggest you to try.
There's totally more information about this on the documentation of react-redux here for mapStatetoProps and here for mapDispatchtoProps which hopefully after reading this article can help you navigate it better.
And that's it for the beginners guide to Redux. If you take the time to really understand these 5 things at least to the point where your comfortable by making your own app with Redux, you'll understand it faster (trust me).
One of the authors of Redux Dan Abramov also gave reasons why we might not need Redux which you can visit here on Medium so I definitely would read it after going through all this laborious work because implementing Redux at the end of the day is still dependent on you and your application's needs unless your work requires it (so you don't really have a choice).
Where do we go from here?
It's totally up to you but I suggest to practice more on this fundamentals before considering other Redux stuff but here you go
Need to make your asynchronous code work with the reducer?
Check out Redux Saga which is what I use at work or Redux ThunkWant to make the processes faster? You know avoiding all those typing?
Check out Reduxsauce or ReduxJS ToolkitRedux hooks will allow you to do a different implementation of Redux on your app, check it out too.
There's really so many more but that's the most important ones.
Thanks for taking the time to read this, hope it helped!
Your support would be very much appreciated. Buying me a coffee would mean a lot
https://www.buymeacoffee.com/royce.chua
Top comments (2)
Thank you very much 🤩. Very helpful
Now, I understand redux
I'm glad it helped