Skip to content

Commit

Permalink
Part 6 screenshots and sandbox
Browse files Browse the repository at this point in the history
  • Loading branch information
markerikson committed Jul 31, 2024
1 parent 945f98a commit e4864ca
Show file tree
Hide file tree
Showing 8 changed files with 31 additions and 10 deletions.
39 changes: 29 additions & 10 deletions docs/tutorials/essentials/part-6-performance-normalization.md
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,28 @@ From there, we know that the list of notifications is in our Redux store state,
### Adding the Notifications List
Now that we've got the `notificationsSlice` created, we can add a `<NotificationsList>` component. It needs to read the list of notifications from the store and format them, including showing how recent each notification was, and who sent it. We already have the `<PostAuthor>` and `<TimeAgo>` components that can do that formatting, so we can reuse them here.
Now that we've got the `notificationsSlice` created, we can add a `<NotificationsList>` component. It needs to read the list of notifications from the store and format them, including showing how recent each notification was, and who sent it. We already have the `<PostAuthor>` and `<TimeAgo>` components that can do that formatting, so we can reuse them here. That said, `<PostAuthor>` includes a "by " prefix which doesn't make sense here - we'll modify it to add a `showPrefix` prop that defaults to `true`, and specifically _not_ show prefixes here.
```tsx title="features/posts/PostAuthor.tsx"
interface PostAuthorProps {
userId: string
// highlight-next-line
showPrefix?: boolean
}

// highlight-next-line
export const PostAuthor = ({ userId, showPrefix = true }: PostAuthorProps) => {
const author = useAppSelector(state => selectUserById(state, userId))

return (
<span>
// highlight-next-line
{showPrefix ? 'by ' : null}
{author?.name ?? 'Unknown author'}
</span>
)
}
```
```tsx title="features/notifications/NotificationsList.tsx"
import { useAppSelector } from '@/app/hooks'
Expand All @@ -443,7 +464,7 @@ export const NotificationsList = () => {
<div key={notification.id} className="notification">
<div>
<b>
<PostAuthor userId={notification.user} />
<PostAuthor userId={notification.user} showPrefix={false} />
</b>{' '}
{notification.message}
</div>
Expand Down Expand Up @@ -673,7 +694,7 @@ export const NotificationsList = () => {
<div key={notification.id} className={notificationClassname}>
<div>
<b>
<PostAuthor userId={notification.user} />
<PostAuthor userId={notification.user} showPrefix={false} />
</b>{' '}
{notification.message}
</div>
Expand Down Expand Up @@ -703,8 +724,6 @@ This does actually show that **it's possible to dispatch an action and not have
Here's how the notifications tab looks now that we've got the "new/read" behavior working:
**[TODO] Update screenshot**
![New notifications](/img/tutorials/essentials/notifications-new.png)
#### Showing Unread Notifications
Expand Down Expand Up @@ -894,7 +913,7 @@ selectPostsByUser(state3, 'user2')
Now that we've memoized `selectPostsByUser`, we can try repeating the React profiler with `<UserPage>` open while fetching notifications. This time we should see that `<UserPage>` doesn't re-render:
**[TODO] React perf profiler screenshot**
![React DevTools Profiler optimized render capture - <UserPage>](/img/tutorials/essentials/userpage-optimized.png)
### Balancing Selector Usage
Expand Down Expand Up @@ -1128,15 +1147,15 @@ First, we import `createEntityAdapter`, and call it to create our `postsAdapter`
`getInitialState()` returns an empty `{ids: [], entities: {}}` normalized state object. Our `postsSlice` needs to keep the `status` and `error` fields for loading state too, so we pass those in to `getInitialState()`.
Now that our posts are being kept as a lookup table in `state.entities`, we can change our `reactionAdded` and `postUpdated` reducers to directly look up the right posts by their IDs via `state.entities[postId], instead of having to loop over the old `posts` array.
Now that our posts are being kept as a lookup table in `state.entities`, we can change our `reactionAdded` and `postUpdated` reducers to directly look up the right posts by their IDs via `state.entities[postId]`, instead of having to loop over the old `posts` array.
When we receive the `fetchPosts.fulfilled` action, we can use the `postsAdapter.setAll` function as the to add all of the incoming posts to the state, by passing in the draft `state` and the array of posts in `action.payload`. This is an example of using the adapter methods as "mutating" helper functions inside of a `createSlice` reducer.
When we receive the `addNewPost.fulfilled` action, we know we need to add that one new post object to our state. We can use the adapter functions as reducers directly, so we'll pass `postsAdapter.addOne` as the reducer function to handle that action. In this case, we use the adapter method _as_ the actual reducer for this action.
Finally, we can replace the old hand-written `selectAllPosts` and `selectPostById` selector functions with the ones generated by `postsAdapter.getSelectors`. Since the selectors are called with the root Redux state object, they need to know where to find our posts data in the Redux state, so we pass in a small selector that returns `state.posts`. The generated selector functions are always called `selectAll` and `selectById`, so we can use destructuring syntax to rename them as we export them and match the old selector names. We'll also export `selectPostIds` the same way, since we want to read the list of sorted post IDs in our `<PostsList>` component.
We could even cut out a couple more lines by changing `postUpdated` to use the postsAdapter.updateOne`method. This takes an object that looks like`{id, changes}`, where `changes` is an object with fields to overwrite:
We could even cut out a couple more lines by changing `postUpdated` to use the `postsAdapter.updateOne` method. This takes an object that looks like`{id, changes}`, where `changes` is an object with fields to overwrite:
```ts title="features/posts/postsSlice.ts"
const postsSlice = createSlice({
Expand Down Expand Up @@ -1577,8 +1596,8 @@ We've built a lot of new behavior in this section. Let's see what how the app lo
<iframe
class="codesandbox"
src="https://codesandbox.io/embed/github/reduxjs/redux-essentials-example-app/tree/checkpoint-4-entitySlices/?codemirror=1&fontsize=14&hidenavigation=1&theme=dark&runonclick=1"
title="redux-essentials-example-app"
src="https://codesandbox.io/embed/github/reduxjs/redux-essentials-example-app/tree/b68c49bccc26768b913e6951b0b5cc3b759f3028?fontsize=14&hidenavigation=1&module=%2fsrc%2Ffeatures%2Fposts%2FpostsSlice.ts&theme=dark&runonclick=1"
title="redux-essentials-example"
allow="geolocation; microphone; camera; midi; vr; accelerometer; gyroscope; payment; ambient-light-sensor; encrypted-media; usb"
sandbox="allow-modals allow-forms allow-popups allow-scripts allow-same-origin"
></iframe>
Expand Down
2 changes: 2 additions & 0 deletions docs/tutorials/essentials/part-8-rtk-query-advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -1175,6 +1175,8 @@ export const NotificationsList = () => {
new: metadata.isNew,
})
// highlight-end

// omit rendering
}
}
```
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/static/img/tutorials/essentials/notifications-new.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/static/img/tutorials/essentials/postslist-optimized.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/static/img/tutorials/essentials/postslist-rerender.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified website/static/img/tutorials/essentials/userpage-rerender.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit e4864ca

Please sign in to comment.