a python/javascript/php/linux guru ready to take on the world...
3 min read
Many people have told me they find it difficult to learn react-redux.
I think it is because even though they may know react,
if they have not yet run into redux it can be very confusing.
So i found it useful to start out learning how to use redux by itself (without react).
After that when you need to connect your store to a react app
you will have a much better idea of what is needed.
So what does that mean? We can make a small command line task list app as an example.
To start lets use npm
or yarn
to install our requirements, for this we will just use redux.
mkdir tasklistcd tasklistyarn init -yyarn add redux uuid prompt
Once that finishes we can start by looking at what it means to use redux.
It calls itself
A predictable state container for javascript apps
Well if you are familiar with react then you are familiar with using state, more traditionally inside of class based Components, but more recently also in functional Components using hooks (which yes, sort of make redux useless, but you want to learn it, maybe because you have to support code using it).
Basically it becomes the single source of truth in your app regarding state. If you need to check a value, you grab it from the redux store. If you want to update a value stored in the state, you pass an action to redux's dispatch function to kick off the update. It becomes very nice when you are working in a big project and you dont need to try to figure out how to update some value in the apps state, ie: which component handles it. You simply find or write the related action creator, and pass its return value to dispatch.
So lets look at some concrete examples, but first lets go over what we need to do:
Figure out what data we need to store
Choose what states we will need to support
Define action type constants to represent our state changes
Combine data and action types into helper functions aka: Action Creators
Write reducer functions to perform our state updates
Wire it into our task app
So now lets think about what we want our app to do.
A task list needs to be able to:
Add a new task with text, and incomplete state
List existing tasks
Set an incomplete task to complete
Delete a task
Now lets define what a task looks like
type Task {id: numbertext: Stringcomplete: Bool}
At some point we should add a due date but for now all we really need is an object with a text property and a property to store its state, ill just call it complete and default it to false.
First we need to import the stuff we want to use
const uuid = require('uuid')const fs = require('fs')const prompt = require('prompt')const { createStore, combineReducers } = require('redux')
Now we will define our action types, in redux an action is an object, with a type and sometimes a payload of data.
An action creator is a function that returns an action.
// action type constantsconst ADD_TASK = 'ADD_TASK'const MARK_COMPLETE = 'MARK_COMPLETE'
Now we can write some functions to return our action objects
// action creator functionsconst addTaskAction = ({ text }) => ({type: ADD_TASK,payload: {text,},})const markCompleteAction = taskId => ({type: MARK_COMPLETE,payload: {taskId,},})
Now we need to write a reducer, which is a function that will take our current state, and an action, and uses it to update the state
const initialState = []const taskListReducer = (state = initialState,{ type, payload: { text, taskId } = {} }) => {switch (type) {case ADD_TASK:return [...state,{text,complete: false,id: uuid(),},]case MARK_COMPLETE:return state.map(task => {if (task.id !== taskId) return taskreturn { ...task, complete: true }})default:return state}}
Now for any of this to last after running, we need to have a way to save the data somehow, so we will just dump json to a file, and read from it to do that.
const loadInitialState = () => {return new Promise((resolve, reject) => {return fs.readFile('./tasks.json', (err, result) => {if (err) {reject(err)}let taskstry {tasks = JSON.parse(result.toString())} catch (e) {tasks = []}resolve({ tasks })})})}const saveState = ({ tasks }) => {const data = JSON.stringify(tasks)try {JSON.parse(data)fs.writeFile('./tasks.json', data, (err, res) => {})} catch (e) {console.log(err)}}
Now to tie redux up and have it ready we need to create our "store
" using the aptly named redux function createStore, which takes 3 arguments:
Your root reducer (the combination of any reducers you have)
Any needed initial state
An "enhancer" which i wont use or go into further here.
Since we want to load our tasks from a file, we need to tie that into creating our store:
const loadStore = () => {return loadInitialState().then(initialState => {return createStore(combineReducers({ tasks: taskListReducer }),initialState)})}
Now that we have a store, that gives us the dispatch
function we can use to execute our actions, so lets write some helper functions to do that for us.
We need functions to:
Add a task
Mark a task complete
Display tasks
const addTask = ({ dispatch }, text) => {dispatch(addTaskAction({ text }))}const markComplete = ({ dispatch }, taskId) => {dispatch(markCompleteAction(taskId))}const displayTasks = tasks => {tasks.forEach(({ text, complete }, index) => {console.log(`${index + 1} [${complete ? 'x' : ' '}] ${text}`)})}
To get our user input, we will use the prompt
libtrary. which takes an object config, namely name
which is how the data is returned, and description
which is the question to ask the user.
const askWhatToDo = [{name: 'whatToDo',description:'what would you like to do?\n[A] Add task\n[L] List tasks\n[C] Complete Task',},]const askWhatTaskToAdd = [{name: 'text',description: 'What should the text of the task be?',},]const askWhatTaskToComplete = [{name: 'taskIndex',description: 'What task Should we mark as complete?',},]
Now lets tie the questions to our functions
When they want to add a task we need to ask what the task is
const doAddTask = store => {prompt.get(askWhatTaskToAdd, (err, { text }) => {console.log(`adding ${text}`)addTask(store, text)})}
To complete a task we need to know which one to mark complete
const doCompleteTask = store => {prompt.get(askWhatTaskToComplete, (err, { taskIndex }) => {const { tasks } = store.getState()const { id, text } = tasks[taskIndex]console.log(`completing ${text}`)markComplete(store, id)})}
Listing the tasks is easy, no questions needed
const doListTasks = store => {const { tasks } = store.getState()displayTasks(tasks)}
Now lets tie it all together along with a question to find out what the user wants to do.
loadStore().then(store => {store.subscribe(() => {const state = store.getState()saveState(state)})prompt.get(askWhatToDo, (err, { whatToDo }) => {const actions = {a: doAddTask,l: doListTasks,c: doCompleteTask,}console.log(`you chose ${whatToDo}`)actions[whatToDo.toLowerCase()](store)})})