# Redux recalls

# understand redux store (learnt from Dan)

// mock a redux store
const mockReduxStore = (reducer) => {
  let state = { counters: 0 }, listeners = [];

  const getState = () => state; // return current latest states
  const dispatch = (action) => {
    state.counters = reducer(state.counters, action); // dispatch send new data to reducer function
    listeners.forEach(listener => listener()); // when states updated, we need to notify every listener we have new state updated !!!!, so we call each of listener and tell them a new state updated result !!
    // console.log('counters value? ', state.counters);
  }

  const subscribe = (listener) => {
    listeners.push(listener); // add new listener into listeners array
    return () => {
      listeners = listeners.filter(l => l !== listener); // remove listener to current listeners array (also called: unsubscribe listener)
    }
  }
  // listeners are used to tracking changes, when changes are requested by dispatch function, list

  dispatch({}); // we want to make initial state populated !!!!

  return { getState, dispatch, subscribe };
};

var store = mockReduxStore(counterReducer);

export default store;
// reference: https://egghead.io/lessons/react-redux-store-methods-getstate-dispatch-and-subscribe

# For simple Redux flow recall

Please check codebase react-redux-typescipt ~~ (For year 2021)

# How to implement Redux Saga (Basic workflow for recall only !!)

// Step 1: create store and connect to the app
// middleware initial
const sagaMiddleware = createSagaMiddleware();
// store
const store = compose(
  applyMiddleware(sagaMiddleware),
  window.devToolsExtension && window.devToolsExtension(),
)(createStore)(rootReducer);
// run sagas
sagaMiddleware.run(rootSaga);

// Step 2: Write Saga functions (API caller + Saga caller)
function getUsersAPICall() {
  return fetch(apiUrl, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/json'
    },
  })
    .then(res => res.json())
    .catch(err => { throw err });
}

function* fetchUsers() {
  try {
    const users = yield call(getUsersAPICall);
    yield put({ type: GET_USERS_SUCCESS, users });
  } catch (error) {
    yield put({ type: GET_USERS_FAILED, message: error.message });
  }
}

export default function* usersSaga() {
  yield takeEvery(GET_USERS_REQUESTED, fetchUsers);
}

// Step 3: write reducer function
const initialState = {
  users: [],
  loading: false,
  error: null,
};

export default function users(state = initialState, action) {
  switch (action.type) {
    case GET_USERS_REQUESTED:
      return {
        ...state,
        loading: true,
      };
    case GET_USERS_FAILED:
      return {
        ...state,
        loading: false,
        error: action.message
      }
    case GET_USERS_SUCCESS:
      return {
        ...state,
        loading: false,
        users: action.users
      }
    default:
      return state;
  }
};

// Step 4: write action function

export function getUsersAction(users) {
  return {
    type: GET_USERS_REQUESTED,
    payload: users,
  }
};

// Step 5: Implement for UI component

export default function User() {
  const dispatch = useDispatch();
  const users = useSelector(state => state.users.users);
  const loading = useSelector(state => state.users.loading);
  const error = useSelector(state => state.users.error);

  useEffect(() => {
    dispatch(getUsersAction());
  }, []);

  return (
    <div>
      {loading && <p>Loading ...</p>}
      {error && !loading && <p>{error}</p>}
      {users && users.length > 0 && !loading && users.map(
        (user, index) => <Card key={`user-${index}`} user={user} />)}
      {users && !loading && users.length === 0 && <p>No users yet ..</p>}
    </div>
  )
}

// For the complete code details, please check codebase: "redux-redux-saga-recall-2021"

# Redux-Thunk workflow example

// store setup

export const store = createStore(
  combineReducers({
    rates: ratesReducer,
  }),
  applyMiddleware(thunk),
);

// reducers

const initialState = {
  amount: '10',
  currencyCode: 'USD',
  currencyData: { USD: 1.0 },
};

export function ratesReducer(state = initialState, action) {
  switch(action.type) {
    case RATES_AMOUNT_CHANGED:
      return {...state, amount: action.payload};
    case RATES_CURRENCY_CHANGED:
      return {...state, currencyCode: action.payload};
    case RATES_RECEIVED:
      return {...state, currencyData: action.payload};
    default:
      return state;
  }
}

// selector functions (access redux state easily)
export const getAmount = state => state.rates.amount;
export const getCurrencyCode = state => state.rates.currencyCode;
export const getCurrencyData = state => state.rates.currencyData;

// actions

export function changeCurrencyCode(currencyCode) {
  return function changeCurrencyCodeThunk(dispatch) {
    dispatch({
      type: RATES_CURRENCY_CHANGED,
      payload: currencyCode
    }); // thunk action creator

    getExchangeRates(currencyCode, supportedCurrencies) // api call
      .then(rates => {
        dispatch({ // then action creator get payload
          type: RATES_RECEIVED,
          payload: rates
        });
      });
  }
};

// thunks

export function getInitialRates(dispatch, getState) {
  const state = getState();
  const currencyCode = getCurrencyCode(state);
  dispatch(changeCurrencyCode(currencyCode)); // fetch currency data before component get loaded ..
};