React Advanced II Notes
React Advanced II Course Introduction
Course Overview
- The course covers advanced React topics, including state management with Redux, React testing, and server-side rendering (SSR).
Curriculum
- Redux for State Management: Learn to integrate Redux with a React application, building upon the Flux pattern.
- React Testing: Explore various methods for testing React components.
- Server-Side Rendering (SSR): Learn to render pages using an SSR server to improve performance and SEO.
Recommended Background
- Basic understanding of HTML, CSS, and JavaScript.
- Experience creating simple UIs with React.
- Interest in exploring related libraries in detail.
- Experience building static pages with HTML/CSS and using JavaScript events to manipulate DOM elements.
Course Objectives
- Implementing Functionalities: Understand how to implement desired functionalities in React using related tools and libraries.
- Technology Selection: Identify and select appropriate technologies based on project goals.
- Understanding React Technologies: Recognize the purpose and necessity of each related React technology.
State Management Using Redux
Table of Contents
- Introduction to Redux
- Redux Structure
- Redux-toolkit Utilization
- Connecting Redux with React
- Handling Asynchronous Operations with Redux
Introduction to Redux
- A library designed to manage the entire state of an application easily.
- Redux borrows many concepts from the Flux pattern.
- Primarily used with React applications.
- Extensive documentation is available at redux.js.org, along with many online resources.
- Numerous examples of Redux being used in real-world app development.
When Should Redux Be Used?
- When managing the entire state of the application is necessary.
- When complex asynchronous processing requires structured state management.
- When the application state becomes too complex and needs a systematic approach.
- When introducing a state management pattern to enable collaboration among multiple developers.
- When tools like logger, devtools, and others are needed to effectively manage and monitor state changes.
Core Principles
- Single Source of Truth: The entire application state is stored in a single store.
- Immutability: The state is read-only; updates require creating a new state object.
- Pure Functions: State changes are handled by pure functions (reducers) that produce no side effects.
Action
- Represents a change in state.
- Typically a JavaScript object containing a
typeand an optionalpayload.
Example:
javascript
const action1 = {
type: 'namespace/getMyData',
payload: { id: 123 }
}
Action Creator
- A function that returns an Action object.
- Improves reusability and adds flexibility by enabling middleware or preprocessing logic.
Example:
javascript
const addObj = (id) => ({
type: 'namespace/getMyData',
payload: { id: String(id).slice(1) }
})
Store
- Stores the state of the entire app.
- The reducer creates a new state based on the Action, and the Store saves that state.
- The state of the Store is immutable.
Example:
javascript
const store = createStore(reducer, initialState)
Reducer
- Receives an Action and creates a new State.
- Follows the interface:
(state, action) => state. - Should have no side effects when changing the state.
Example:
javascript
const reducer = (state, action) => {
switch (action.type) {
case 'namespace/getMyData':
const obj = { id: action.payload.id }
return { ...state, obj }
default:
return state
}
}
const store = createStore(reducer, initialState)
Dispatch
- A function for sending Actions to Redux.
- After dispatching, the action passes through the middleware and reaches the reducer.
Example:
javascript
function MyApp() {
const dispatch = useDispatch()
return (
<button onClick={() => dispatch(addObj(1234))}>Submit</button>
)
}
Selector
- A function used to retrieve specific pieces of state from the Redux store.
- Selectors can access raw state values or computed data.
- Helps organize and optimize how state is accessed across components.
javascript function MyApp() { const obj = useSelector(state => state.obj) return ( <div>{JSON.stringify(obj)}</div> ) }
Redux Structure
- Redux is highly flexible and can be freely extended as needed.
- Understanding how actions and data flow internally allows you to extend Redux using middleware, enhancers, and other tools.
Middleware
- Intercepts actions after they are dispatched and before they reach the reducer.
- Libraries like redux-thunk and redux-logger are commonly used.
Enhancer
- Wraps the store and can modify how actions are processed, typically after all middleware has run and before reaching the reducer.
- Libraries such as Redux DevTools are applied as enhancers.
Redux-toolkit Utilization
- Redux Toolkit is the officially recommended helper library for Redux.
- Eliminates much of the boilerplate code previously required.
- Simplifies writing Redux logic.
- Includes useful libraries by default: redux-devtools, immerjs, redux-thunk, and reselect.
Redux-toolkit API - configureStore
- A wrapper around Redux’s
createStorefunction. - Simplifies store creation using named parameters.
- The
reducerkey accepts an object, andconfigureStoreautomatically appliescombineReducersinternally.
Example:
javascript
const store = configureStore({
reducer: {
posts: postsReducer,
users: usersReducer
}
})
Redux-toolkit API - createAction
- An action creator is a function that returns an action object.
- Data passed to the action creator is included in the
payloadfield of the action. - The generated action creator overrides the
toString()method to return the action’s type string.
Example:
javascript
const addPost = createAction('post/addPost')
addPost({ title: 'post 1' })
/*
{
type: 'post/addPost',
payload: { title: 'post 1' }
}
*/
Redux-toolkit API - createReducer
- A reducer is created using the
createReducerfunction. - The
builder.addCasemethod is used to define how the state should change for each specific action. - Internally, Immer.js is used, allowing you to write code that appears to mutate the state directly while maintaining immutability.
Example:
javascript
const postsReducer = createReducer(initState, builder => {
builder.addCase(addPost, (state, action) => {
state.posts.push(action.payload)
})
})
Redux-toolkit API - createSlice
- A slice combines action creators and reducers into a single object.
- The
createSlicefunction simplifies Redux logic by reducing boilerplate code. - It automatically generates the action creators and reducer based on the configuration provided.
Example:
javascript
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
addPost(state, action) {
state.posts.push(action.payload)
}
}
})
const { addPost } = postsSlice.actions
const reducer = postsSlice.reducer
Redux-toolkit API - createSelector
- The
createSelectorfunction is used to derive specific data from the Redux state. - It allows for memoized selectors— if the input data hasn’t changed, the cached result is returned instead of recalculating.
Example:
javascript
const postsSelector = state => state.posts
const userSelector = state => state.user
const postsByUserIdSelector = createSelector(
postsSelector,
userSelector,
(posts, user) => posts.filter(post => post.username === user.username)
)
Connecting Redux with React
- A library used to connect Redux with a React application.
- Allows components to access Redux-managed state and dispatch actions.
- Compatible with both class components and functional components.
React-redux API - Provider
- To connect the Redux store to a React application, the root component must be wrapped with the Provider component from react-redux.
- Any component rendered within the Provider can access the Redux state and dispatch actions.
javascript const store = configureStore({ reducer: rootReducer }) function App() { return ( <Provider store={store}> <MyPage /> </Provider> ) }
React-redux API - useDispatch
- The
useDispatchhook is used to access the Redux dispatch function. - By calling
dispatch(action), the generated action is sent to the Redux store to update the state. - Typically used in response to user interactions, such as button clicks.
Example:
javascript
const addPost = createAction('addPost')
function MyPage() {
const dispatch = useDispatch()
const handleClick = () => dispatch(addPost())
return (
<button onClick={handleClick}>Submit</button>
)
}
React-redux API - useSelector
- The
useSelectorhook is used to retrieve data from the Redux store. - A selector function is passed as an argument to specify which part of the state to access.
- The selector function must be pure and should not modify the state.
- Data can be derived or transformed into a specific format before being returned.
Example:
javascript
function MyPage() {
const posts = useSelector(state => state.posts)
return posts.map(post => <Post {...post} />)
}
Handling Asynchronous Operations with Redux
- To handle asynchronous processing in Redux, it is necessary to add middleware.
redux-thunkis a middleware that simplifies handling asynchronous actions by using Promises.
createAsyncThunk
redux-toolkitincludes thunk middleware by default.redux-toolkitprovides thecreateAsyncThunkAPI for handling asynchronous actions.- It automatically manages three states: pending, fulfilled, and rejected.
- In a TypeScript environment, using a builder callback when writing reducers is necessary to ensure accurate typing.
createAsyncThunktakes two arguments: an action type and an async callback (the payload creator).- When an action type is provided, it automatically generates three lifecycle action types: pending, fulfilled, and rejected. These are used as postfixes and dispatched to reducers. Example:
posts/addPost/pending
Example: ```javascript const addPost = createAsyncThunk('posts/addPost', async (title) => { const result = await PostAPI.addPost({ title }) return result.data })
// Component
useEffect(() => {
dispatch(addPost("post 1"))
}, [])
```
Using
createAsyncThunk, the created action creator consists of four functions:addPost: An async function used to dispatch the action.addPost.pending: Dispatched when the promise is still pending.addPost.fulfilled: Dispatched when the promise resolves successfully.addPost.rejected: Dispatched when the promise is rejected due to an error.
The
extraReducersfunction ofcreateSliceallows you to handle additional action types by adding them to the builder.The builder pattern is officially recommended because it makes TypeScript typing easier and more manageable.
javascript const postsSlice = createSlice({ // ... extraReducers: builder => { builder .addCase(addPost.pending, state => ...) .addCase(addPost.fulfilled, state => ...) .addCase(addPost.rejected, state => ...) } }) When fulfilled, the data is received as the payload, e.g.,
action.payload.todos.When rejected, the error is received as
action.error, and in this case,action.payloadbecomes undefined.
Continuous Asynchronous Processing
- When a thunk function is dispatched, it returns a Promise.
- Therefore, the
.then()method can be used to chain asynchronous operations sequentially.
Example:
javascript
dispatch(addPost("post1"))
.then(() => dispatch(updatePost("post2")))
Concurrent Asynchronous Processing
- By using
Promise.all, multiple asynchronous operations can be executed concurrently. - Note: If any thunk’s Promise is rejected, the
.then()block will not execute.
Example:
javascript
Promise.all([
dispatch(addPost("post1")),
dispatch(updatePost("post2"))
])
.then(() => console.log("DONE"))