The purpose of this app is to ensure that I absolutely understand:
- the purpose of reducers
- making API requests with Redux
- the purpose of redux-thunk
This app is not intended to showcase UI/UX. It is a study to further my knowledge of how to implement react with redux.
Node.js
create-react-app
React
Redux
React-Redux
Redux-Thunk
Visual Studio
JSON Placeholder API
smeantic-ui
Lodash Memoize
Semantic-UI https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.4.1/semantic.min.css
npm install npm start
// example: to pass ONE user to the component
const mapStateToProps = (state, ownProps) => {
return { users: state.users.find(user => user.id === ownProps.userId)}
}
- so not instead of passing all the users with the find() method directly in the component by
accessing the redux stroe with this.props.user, the find() method is called within
mapStateToProps by passing in ownProps to access the store
- you then can access ONE user with const { user } = this.props; (this.props.user) within the component
- mapStateToProps is supposed to do work on the state + props!
- Us on action to prevent overfetching of data from API ==> optimization
- _.memoization -> one way to solve overfetching, unique
- _.uniq(._map) => fetch only unique keys from jsonPlaceholder --> like memoization
- OR use _.chain:
_.chain(getState.name) // used to "chain on" additonal funcitons for a response
.map('userId')
.uniq()
.forEach(id => dispatch(fetchUser(id)))
.value() // executes all the steps
- To change state of App --> Action Creator -->
- action creator must return plain JS objects with a type property
- axios + async, await causes ^^this^^ error in the browser
- bc of babel transpiling ot ES2015
- solution => redux-thunk middleware
const createAction() = (x, y) => {
return {
type: 'CREATE_ACTION',
payload: {
xName: x,
yName: y
OR just
x, y
}
}
}
Produces an... -->
2. Action --> Gets Fed to... -->
3. dispatch --> middleware --> Forwards an action to... -->
- Middleware in Redux
- Function that gets called with every action dispatched
- ability to STOP, MODIFY, or mess around with actions
- most popular usage ==> asnyc actions
- Reducers -->
const reducerName = (oldInfo = [], action) => {
if(action.type === 'ACTION_NAME') {
// we care about this action
// ...oldInfo copied and added to action.payload
// creates a brand new array
// oldInfo.push(action.payload) only adds to an existing array
// NEVER USE THIS === BAD DATA
return [...oldInfo, action.payload]
};
// we dont care about this action
return oldInfo;
}
Creates new... -->
5. State --> Wait until need to update state again (newAction)
- Store = actions and reducers
const { createStore, combineReducers } = Redux;
const ourDepartments = combineReducers({
name1: name1,
name2: name2,
name3: name3
})
const store = createStore(ourDepartments);
// call action
const action = createAction('x', 'y');
store.dispatch(action);
// get state
store.getState();
- Components are generally responsible for fetching data they need
by calling an action creator
- Component gets rendered
- Component's componentDidMount lifecycle method called
- Call action creator from componentDidMount
- Action creators are responisble for making API requests
- redux-thunk works here
- Action creator runs code to make API request
- API responds with data
- Action creator returns 'action' with fetched data on the 'payload' property
- Fetched data shows in a component with new state in redux store,
mapStateToProps
- Some reducer sees the action, returns the data off the 'payload'
- Because of new state object, redux/react-redux cause rerender
- overview:
export default (state = [], action) => {switch (action.type) {case 'FETCH_N'...default: return state}};
- must return any value besides 'undefined'
- produces 'state', or data to be used inside of app
- using only previous state and the action (reducers are pure!)
- Must not return reach 'out of itself' to decide what value to return
- BAD
return document.querySelector(...)
- BAD
return axios.get(...)
- GOOD
return state + action
- BAD
- Should not mutate its input 'state' argument
- mutate - change of contents of data
- BAD
state[0] = newValue
- BAD
state.push(...)
- this is not ok because of how data is stored in memory not the actual values
- Redux snippet from source code for combineReducer:
let hasChanged = false const nextState = {} for (let i=0; i < finalReducerKeys.length; i++) { const key = final ReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) if (typeof nextStateForKey = 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw error Error(errorMessage) } nextState[key] = nextStateForKey /* * this is the part that gets the next state or not * if this is still false then the function returns there is no change * therefore returning old state /* hasChanged = hasChanged || nextStateForKey !== previousStateForKey } return hasChanged ? nextState : state
- Syntax best practice:
// have default value for selectedItem to avoid undefined error
const selectedItemReducer = (selectedItem = null, action) => {
// if correct return payload
if(action.type === 'SELECTED_ITEM') {
return action.payload
}
// else return deault value
return selectedItem
}
REDUCER | BAD | GOOD |
---|---|---|
Remove el from array | state.pop() | state.filter(element=>element!=='hi') |
Adding an el to array | state.push('hi) | [...state, 'hi'] |
Replacing an el in array | state[0]='hi' | state.map(el=> el==='hi' ? 'bye':el) |
Updating a property Obj | state.name = "Sam" | {...state, name: 'Sam'} |
Adding a property to Obj | state.age = 30 | {...state, age:30} |
Removing a property from Obj | delete state.name | {...state, age:undefined} |
- integration between react and redux
- middleware to help make requests in a redux application
- most popular use async funcitons
- allows the option for actions to return a function
dispatch --> Action Creator Obj or Function
/\
/ \
Obj Fxn
/ \
to reducers called with dispatch and getState -->
manually dispatch fxn when request is finished -->
New Action! --> back to top dispatch with returned request
From Source Code This is the function that redux thunk is using to help with async
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Normal Rules for Actions | Redux Thunk Rules |
---|---|
Action creators must return action objects | Action creators can return action objects or Functions! |
Actions must have a type property | If Obj gets returns, must have a type |
Actions can optionally have a 'payload' | If an obj type gets returned, payload is optional |