-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Revamp "Redux Essentials" tutorial to be TS-first and update contents #4706
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. |
✅ Deploy Preview for redux-docs ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
d107755
to
36dfbeb
Compare
b638fb2
to
8aa8994
Compare
```ts title="features/posts/postsSlice.ts" | ||
// highlight-start | ||
// Import the `PayloadAction` TS type | ||
import { createSlice, PayloadAction } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { createSlice, PayloadAction } from '@reduxjs/toolkit' | |
import type { PayloadAction } from '@reduxjs/toolkit' | |
import { createSlice } from '@reduxjs/toolkit' |
Explicit type imports could help better distinguish between TS/JS related material on a quick glance.
```js title="features/posts/postsSlice.js" | ||
```ts title="features/posts/postsSlice.ts" | ||
// highlight-next-line | ||
import { createSlice, PayloadAction } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { createSlice, PayloadAction } from '@reduxjs/toolkit' | |
import type { PayloadAction } from '@reduxjs/toolkit' | |
import { createSlice } from '@reduxjs/toolkit' |
```js title="features/users/usersSlice.js" | ||
import { createSlice } from '@reduxjs/toolkit' | ||
```ts title="features/users/usersSlice.ts" | ||
import { createSlice, PayloadAction } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { createSlice, PayloadAction } from '@reduxjs/toolkit' | |
import type { PayloadAction } from '@reduxjs/toolkit' | |
import { createSlice } from '@reduxjs/toolkit' |
```jsx title="features/posts/ReactionButtons.js" | ||
import React from 'react' | ||
```ts | ||
import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit' | |
import type { PayloadAction } from '@reduxjs/toolkit' | |
import { createSlice, nanoid } from '@reduxjs/toolkit' |
In this case, our auth state is really just the current logged-in username, and we'll reset it to `null` if they log out. | ||
|
||
```ts title="features/auth/authSlice.ts" | ||
import { createSlice, PayloadAction } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { createSlice, PayloadAction } from '@reduxjs/toolkit' | |
import type { PayloadAction } from '@reduxjs/toolkit' | |
import { createSlice } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I may apply the type
import change near the end, but I'm honestly not worried about that for now
Given that, we can import the `userLoggedOut` action from `authSlice.ts` into `postsSlice.ts`, listen for that action inside of `postsSlice.extraReducers`, and return an empty posts array to reset the posts list on logout: | ||
|
||
```ts title="features/posts/postsSlice.ts" | ||
import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
import { createSlice, nanoid, PayloadAction } from '@reduxjs/toolkit' | |
import type { PayloadAction } from '@reduxjs/toolkit' | |
import { createSlice, nanoid } from '@reduxjs/toolkit' |
6689b10
to
8bd4f9b
Compare
Implementing Streaming Updates This line needs to be highlighted: const notificationsReceived = createAction<ServerNotification[]>('notifications/notificationsReceived') I didn't see that I needed to add it, then I ran into an error when forming the |
Implementing Streaming Updates const message: {
type: 'notifications'
payload: ServerNotification[]
} = JSON.parse(event.data) I'm not sure why you are making a custom type for the const message: PayloadAction<ServerNotification[]> = JSON.parse(event.data) |
Implementing Streaming Updates catch {
// no-op in case `cacheEntryRemoved` resolves before `cacheDataLoaded`,
// in which case `cacheDataLoaded` will throw
} The wording on this error handler comment is confusing. This is my understanding of that comment:
|
I've completed the tutorial now. Due to the error, I can't tell if I've completed it successfully or not. The new tutorial was much easier to follow though and I felt far less intimidated working through it this time. The web-socket portion is still complex. The comments throughout that code block go a long way to walking the reader through that bit of code though. The code-block sizes are much more reasonable now as well. I felt guided through the tutorial this time which is a huge improvement. |
@Dan503 : Ah, yeah, the issue is that it isn't supposed to be functional yet. The websocket connection doesn't get activated until the section after this, so it's expected that the button is actually "broken" at this point in the sequence. It still needs the I can try to clarify the instructions here, as well as maybe add an alert or log message if the socket doesn't exist yet. |
Mostly because I wasn't thinking of it that way :) The mock server backend could send any arbitrary data over the socket. I just happened to structure its message similarly to how a Redux action looks out of habit. Since I wasn't thinking of it as an actual action, it didn't occur to me to reuse |
Ok... I was still getting the error by the time I reached the end of the tutorial though 😕 |
@Dan503 : checked out your code. I don't see the I suspect that the issue might have something to do with hot module reloading, but I'm not entirely sure. Behavior-wise, I see a bug in the So, I think the example repo code and instructions are correct at this point, but I'll give them another look-through. |
@markerikson I'm not sure what you mean by that. The code written in the tutorial doesn't wrap It writes it like this: switch (message.type) {
case 'notifications': {
lifecycleApi.updateCachedData((draft) => {
// Insert all received notifications from the websocket
// into the existing RTKQ cache array
draft.push(...message.payload)
draft.sort((a, b) => b.date.localeCompare(a.date))
})
break
}
default:
break
} My code: if (message.type === 'notifications') {
lifecycleApi.updateCachedData((draft) => {
// Insert all received notifications from the websocket
// into the existing RTKQ cache array
draft.push(...message.payload)
draft.sort((a, b) => b.date.localeCompare(a.date))
})
} |
Ah, yeah. Sorry, getting things mixed up :) I'll have to look further to see why your code isn't getting the notifications saved right. I see the websocket messages going through... |
Ah, I see it! The notifications are getting added fine, but // Dispatch an additional action so we can track "read" state
lifecycleApi.dispatch(notificationsReceived(message.payload))
break so the So, to recap:
|
I noticed a bug in my code. I pushed up a fix for it. I didn't store the current user correctly when logging in so |
@markerikson I've updated my listener code to this and it's working now :) // when data is received from the socket connection to the server,
// update our query result with the received message
function listener(event: MessageEvent<string>) {
const message: PayloadAction<Array<ServerNotification>> = JSON.parse(
event.data,
)
if (message.type === 'notifications') {
lifecycleApi.updateCachedData((draft) => {
// Insert all received notifications from the websocket
// into the existing RTKQ cache array
draft.push(...message.payload)
draft.sort((a, b) => b.date.localeCompare(a.date))
})
// Dispatch an additional action so we can track "read" state
lifecycleApi.dispatch(notificationsReceived(message.payload))
}
} So the instructions were missing this line of code // Dispatch an additional action so we can track "read" state
lifecycleApi.dispatch(notificationsReceived(message.payload))
break |
Yep, my bad, I see I did forget to include those lines there! Updating that locally. |
So the instruction clarification
And the
Are the only two changes that the documentation needs (along with any "TODO" items you may have left for applying images or other things like that) |
Oh also the highlighting of the following line of code as mentioned in #4706 (comment) const notificationsReceived = createAction<ServerNotification[]>('notifications/notificationsReceived') That is a small though important change or it will confuse a huge number developers doing the tutorial. |
Okay, I think this is done!
I'm sure there's leftover typos and things that could be improved, but I think this is good enough to merge and launch at this point! @Dan503 THANK YOU SO MUCH FOR YOUR DETAILED REVIEW FEEDBACK! You made this drastically better! |
@markerikson the timing ended up working out ridiculously well... like insanely well...
So yeah, a lot of converging coincidences to make this thing happen 🤯 |
Okay. The app repo at https://github.com/reduxjs/redux-essentials-example-app now has LET'S DO THIS! For the record, some word count stats: Existing Content
Total: 43517 Revised Content
Total: 52358 |
Actual content changes for #4393 , at long last!
The current WIP example code is over in:
I'm doing another round of revisions and step-by-step checking to those code commits as I rework the tutorial content, but that should be the progression and code content I want to show off in the tutorial.
Big picture summary:
createListenerMiddleware
and thunks increateSlice