Graduate Program KB

Redux

  • A predictable container for Javascript applications, often used wth React. Redux provides a central location to store the state of an application and allows components to access and modify the state.
  • The data is stored in memory and volatile, when the application is closed so will the data.
  • Structure
  • You can import Redux with a library and it would have similar functionality as the example below.
    • Entire state is stored in a single immutable object called the Store
    • You can update/modify the state with the use of Dispatchers
    • You can get data from the store with useSelector
/**
 * 1. The state
 * 2. Get the state (getState)
 * 3. Listen to the changes on the state (subscribe)
 * 4. Update the state (dispatch)
 * This file is a recreation of the Real Redux functionality
 **/

//Action Creators
//Actions are just object representations of different functions
function addTodoAction (todo) {
    return ({
        type: "ADD_TODO",
        todo,
    })
}

//Action Creators/Destroyers
function removeTodoAction(id) {
    return ({
        type: "REMOVE_TODO",
        id,
    })
}

//Action
function toggleTodo(id) {
    return ({
        type: "TOGGLE_TODO",
        id,
    })
}

//Action Creators
function addGoalAction (goal) {
    return ({
        type: "ADD_GOAL",
        goal,
    })
}

//Action Creators
function removeGoalAction (id) {
    return ({
        type: "REMOVE_GOAL",
        id,
    })
}

//Middleware
/* function checker(store) {
    return function (next) {
        return function (action) {
            //Middleware Code
            if(action.type === 'ADD_TODO' 
                && action.todo.name.toLowerCase().indexOf('bitcoin') !== -1){
            return alert('Bad Idea')}
    
            if(action.type === 'ADD_GOAL' 
                && action.goal.name.toLowerCase().indexOf('bitcoin') !== -1){
            return alert('Bad Idea')}
        return next(action)
        }
    }
} */

//Arrow functions, same as above
const checker = (store) => (next) => (action) => {
    if(action.type === 'ADD_TODO' 
                && action.todo.name.toLowerCase().indexOf('bitcoin') !== -1){
            return alert('Bad Idea')}
            if(action.type === 'ADD_GOAL' 
                && action.goal.name.toLowerCase().indexOf('bitcoin') !== -1){
            return alert('Bad Idea')}
        return next(action)
}

//Middleware to log events to console
/**
 * 
 */
const logger = (store) => (next) => (action) => {
    console.group(action.type)
    console.log("The action: ",action) //Happens before the dispatch happesn
    const result = next(action) //dispatch(action)
    console.log("The new state is: ", store.getState()) //Happens After
    console.groupEnd()
    return result; //Can be used anywhere i guess
}

//4 Updating state
//Pure Function are always predictable
//1. Always return same result if the same arguments are passed in
//2. Depends only on the arguments passed into them
//3. Never products any side effects

//Reducer Function for todo (Pure Function)
//Takes current state of the store and performs an action
function todos(state = [],action) {
    if (action.type === 'ADD_TODO') {
        return state.concat([action.todo]) //Creates brand new array
    } else if (action.type === 'REMOVE_TODO') {
        return state.filter((todo) => todo.id !== action.id) //Removes from the state
    } else if (action.type === 'TOGGLE_TODO') {
        return state.map((todo) => todo.id !== action.id ? todo : 
            Object.assign({},todo,{complete: !todo.complete}) //Toggles the complete status betwee true or false
        )
    }
    else {
        return state
    }
    //Can use a switch case statement
}

//Reducer Function
function goals(state = [],action) {
      switch(action.type) {
        case 'ADD_GOAL':
            return state.concat([action.goal])
        case 'REMOVE_GOAL':
            return state.filter((goals) => goals.id !== action.id)
        default:
            return state
      }
      //Returns a new state object thats why it is a pure function
}

//Route Reducer, same as Redux.combineReducers
function app(state = {},action) {
    return {
        todos:todo(state.todos,action), //Reducers
        goals:goals(state.goals,action)
    }
}


//Library Code of Redux
function createStore (reducer){

    let state //1 The data is stored in the state/store rather than in the DOM itself
    let listeners = [] //3

    const getState = () => state //2

    //when user calls subscribe passing a function, that function will
    //be pushed into the listeners array
    //when first call, the return statement will remove intial function passed in
    const subscribe = (listener) => { //3
        listeners.push(listener)
        return () => {
            listeners = listeners.filter((l) => l !== listener)
        }
    }

    //To update state 4
    const dispatch = (action) => {
        // Call todo function to get new state
        // Loop over listeners and invoke them
        state = reducer(state,action)
        listeners.forEach((listener) => listener()) //Invoke each listener that state has been changed
    }

    return {
        getState,
        subscribe,
        dispatch,
    }
}

//Create a Store
const store = Redux.createStore(Redux.combineReducers({
    todos,
    goals,
  }), Redux.applyMiddleware(checker,logger))
  //2nd argument is middleware

//Function to generate random id for todos and goals
function generateRandomId() {
    return Math.random().toString(36).substring(2) + (new Date().getTime().toString(36));
}



//DOM Code for HTML files
function createRemoveButton (onClick) {
    
    const removeBtn = document.createElement('button')
    removeBtn.innerHTML = 'X'
    removeBtn.addEventListener('click',onClick)
    return removeBtn
}

function addTodo() {
    const input = document.getElementById('todo')
    const name = input.value
    input.value = ''

    store.dispatch(addTodoAction({
        id:generateRandomId(),
        name,
        complete: false,
    }))
}

function addGoal() {
    const input = document.getElementById('goal')
    const name = input.value
    input.value = ''

    store.dispatch(addGoalAction({
        id:generateRandomId(),
        name,
    }))
}

store.subscribe(() => {
    const {goals,todos} = store.getState()

    //Resets the previous list in the DOM
    document.getElementById('goals').innerHTML = ''
    document.getElementById('todos').innerHTML = ''

    goals.forEach(addGoalToDom)
    todos.forEach(addToDoToDom)
})

document.getElementById('btnAddTodo').addEventListener('click',addTodo)
document.getElementById('btnAddGoal').addEventListener('click',addGoal)

function addToDoToDom(todo) {
    const node = document.createElement('li')
    const text = document.createTextNode(todo.name)

    const removeBtn = createRemoveButton(() => {
        store.dispatch(removeTodoAction(todo.id))
    })

    node.appendChild(text)
    node.appendChild(removeBtn)
    node.style.textDecoration = todo.complete ? 'line-through' : 'none'
    node.addEventListener('click',() => {
        store.dispatch(toggleTodo(todo.id))
    })
    document.getElementById('todos').appendChild(node)
}

function addGoalToDom(goal) {
    const node = document.createElement('li')
    const text = document.createTextNode(goal.name)
    const removeBtn = createRemoveButton(() => {
        store.dispatch(removeGoalAction(goal.id))
    })
    node.appendChild(text)
    node.appendChild(removeBtn)
    document.getElementById('goals').appendChild(node)
}