You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: docs/tutorials/essentials/part-4-using-data.md
+47-1Lines changed: 47 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -1052,7 +1052,7 @@ We should now see both sides of the auth behavior working:
1052
1052
- If the user tries to access `/posts` without having logged in, the `<ProtectedRoute>` component will redirect back to `/` and show the `<LoginPage>`
1053
1053
- When the user logs in, we dispatch `userLoggedIn()` to update the Redux state, and then force a navigation to `/posts`, and this time `<ProtectedRoute>` will display the posts page.
1054
1054
1055
-
### Showing the Logged-In User
1055
+
### Updating the UI with the Current User
1056
1056
1057
1057
Since we now know who is logged in while using the app, we can show the user's actual name in the navbar. We should also give them a way to log out as well, by adding a "Log Out" button.
Finally, it also doesn't make sense to allow the current user to edit posts defined by _other_ users. We can update the `<SinglePostPage>` to only show an "Edit Post" button if the post author ID matches the current user ID:
1140
+
1141
+
```tsxtitle="features/posts/SinglePostPage.tsx"
1142
+
exportconst SinglePostPage = () => {
1143
+
const { postId } =useParams()
1144
+
1145
+
const post =useAppSelector(state=>
1146
+
state.posts.find(post=>post.id===postId)
1147
+
)
1148
+
// highlight-next-line
1149
+
const user =useAppSelector(state=>state.auth.username)
There's one more piece of the auth handling that we need to look at. Right now, if we log in as user A, create a new post, log out, and then log back in as user B, we'll see both the initial example posts and the new post.
@@ -217,57 +217,72 @@ So far, our `postsSlice` has used some hardcoded sample data as its initial stat
217
217
218
218
In order to do that, we're going to have to change the structure of the state in our `postsSlice`, so that we can keep track of the current state of the API request.
219
219
220
-
### Extracting Posts Selectors
220
+
### Extracting Selectors for Slices
221
221
222
222
Right now, the `postsSlice` state is a single array of `posts`. We need to change that to be an object that has the `posts` array, plus the loading state fields.
223
223
224
224
Meanwhile, the UI components like `<PostsList>` are trying to read posts from `state.posts` in their `useSelector` hooks, assuming that field is an array. We need to change those locations also to match the new data.
225
225
226
-
It would be nice if we didn't have to keep rewriting our components every time we made a change to the data format in our reducers. One way to avoid this is to define reusable selector functions in the slice files, and have the components use those selectors to extract the data they need instead of repeating the selector logic in each component. That way, if we do change our state structure again, we only need to update the code in the slice file.
226
+
It would be nice if we didn't have to keep rewriting our components every time we made a change to the data format in our reducers. One way to avoid this is to **define reusable selector functions in the slice files**, and have the components use those selectors to extract the data they need instead of repeating the selector logic in each component. That way, if we do change our state structure again, we only need to update the code in the slice file.
227
227
228
-
The `<PostsList>` component needs to read a list of all the posts, and the `<SinglePostPage>` and `<EditPostForm>` components need to look up a single post by its ID. Let's export two small selector functions from `postsSlice.js` to cover those cases:
228
+
#### Defining Selector Functions
229
+
230
+
You've already been writing selector functions every time we called `useAppSelector`, such as `useAppSelector( state => state.posts )`. In that case, the selector is being defined inline. Since it's just a function, we could also write it as:
Selectors are typically written as standalone individual functions in a slice file. They normally accept the entire Redux `RootState` as the first argument, and may also accept other arguments as well.
238
+
239
+
#### Writing Posts Selectors
240
+
241
+
The `<PostsList>` component needs to read a list of all the posts, and the `<SinglePostPage>` and `<EditPostForm>` components need to look up a single post by its ID. Let's export two small selector functions from `postsSlice.ts` to cover those cases:
Note that the `state` parameter for these selector functions is the root Redux state object, as it was for the inlined anonymous selectors we wrote directly inside of `useSelector`.
260
+
Note that the `state` parameter for these selector functions is the root Redux state object, as it was for the inlined anonymous selectors we wrote directly inside of `useAppSelector`.
It's often a good idea to encapsulate data lookups by writing reusable selectors. You can also create "memoized" selectors that can help improve performance, which we'll look at in a later part of this tutorial.
304
+
#### Extracting Auth and Users Selectors
305
+
306
+
While we're at it, we also have several more components that have inlined selectors for accessing `state.auth` and `state.users`. That includes multiple components that are checking the current logged-in username or getting the current user object.
307
+
308
+
We can extract those into reusable selectors in their respective slices as well:
Notice that `selectCurrentUser` actually makes use of the `selectCurrentUsername` selector from the auth slice! Since selectors are just normal functions, they can call each other to look up necessary pieces of data from the state.
345
+
346
+
Once we've written these new selectors, we can replace all of the remaining inlined selectors in our components with the matching selectors from the slice files.
347
+
348
+
#### Using Selectors Effectively
349
+
350
+
It's often a good idea to encapsulate data lookups by writing reusable selectors. Ideally, components don't even have to know where in the Redux `state` a value lives - they just use a selector from the slice to access the data.
351
+
352
+
You can also create "memoized" selectors that can help improve performance by optimizing rerenders and skipping unnecessary recalculations, which we'll look at in a later part of this tutorial.
290
353
291
354
But, like any abstraction, it's not something you should do _all_ the time, everywhere. Writing selectors means more code to understand and maintain. **Don't feel like you need to write selectors for every single field of your state**. Try starting without any selectors, and add some later when you find yourself looking up the same values in many parts of your application code.
292
355
356
+
#### Optional: Defining Selectors Inside of `createSlice`
357
+
358
+
We've seen that we can write selectors as standalone functions in slice files. In some cases, you can shorten this a bit by defining selectors directly inside `createSlice` itself.
We've already seen that `createSlice` requires the `name`, `initialState`, and `reducers` fields, and also accepts an optional `extraReducers` field.
363
+
364
+
If you want to define selectors directly inside of `createSlice`, you can pass in an additional `selectors` field. The `selectors` field should be an object similar to `reducers`, where the keys will be the selector function names, and the values are the selector functions to be generated.
365
+
366
+
**Note that unlike writing a standalone selector function, the `state` argument to these selectors will be just the _slice state_, and _not_ the entire `RootState`!**.
367
+
368
+
There _are_ still times you'll need to write selectors as standalone functions outside of `createSlice`. This is especially true if you're calling other selectors that need the entire `RootState` as their argument, in order to make sure the types match up correctly.
369
+
370
+
Here's what it might look like to convert the users slice selectors to be defined inside of `createSlice`:
371
+
372
+
```ts
373
+
const usersSlice =createSlice({
374
+
name: 'users',
375
+
initialState,
376
+
reducers: {},
377
+
// highlight-start
378
+
selectors: {
379
+
// Note that `state` here is just the `UsersState`!
0 commit comments