📱

Get Our Mobile App

Take your business learning on the go!

Download on the App StoreGet it on Google Play

the most important Next.js features to learn (in 8 minutes)

Web Dev Cody8:26

Transcription

I'm going to try to teach you as much as I can in five minutes about Next.js. First step is, don't read the docs. Let's just go over here to a terminal and say `npx create-next-app@latest` and we'll call it `my-app`. Just go ahead and spam the enter key and make an application. Go ahead and wait a couple minutes for npm to finish installing all of its packages. Let's open up this `app` directory. Let's go ahead and enter that `app` directory and say `npm run dev`.

Go ahead and click on `localhost:3000` in the terminal, and you'll see that the Next.js app is running. So, the first important thing to understand about Next.js is that it uses a file-based routing. So, inside `src/app`, you have these `page.tsx` files, and these are used to declare what is going to be on your page. In our case, let's just get rid of all this extra boilerplate because we don't need it, and I'll just say "Hello, world!" and just verify this shows up.

So, let's make a new route. I'm going to call it `users`, and we're going to go ahead and just make a new `page.tsx` here. Let's copy this page, and I want to show you that you can make a new page as simple as this: say `users` page. So now we can go to the root route and just say `/users`, and notice that that new page shows up.

So now, let's learn about data fetching. So typically, you have a data access directory, and I'm going to go ahead and just say put a `users.ts` file in here. I have some code that basically just stores and retrieves from a global storage so that when Next.js is restarting, I don't lose this data. But we have two methods: `getUser` and `updateUser`. Let's try to use them in our React server component.

When a page loads, often you want to fetch data. So how do you do that? Well, put `async` in front of this. I can say `const user = await getUser(...)` like this. But we need to pass it an ID. The way you can get the ID is you can actually add another folder here called `[userId]`, and this allows you to get path parameters. So if I go up here and say `params`, I can simply say `params.userId`. This is complaining because we haven't typed this. So let's just go ahead and add some TypeScript types here so that we know that a `userId` should be passed over in the path parameters. And remember to `await` on any promises. And we can actually just display this name right here.

If we go back to the app and now type in, like, `50`, we should see `Users: John Doe`. Now, the key thing to point out is that this technically is coming from an external data source if you're using, like, a database. So that's how you can actually fetch data when your React server component is running through.

But let's kind of take it to the next step. How do you store data? Let's make a form here, and I'll give it an `action`. And inside this action, you can actually just give it a `use server` anonymous function. And then we can go ahead and put an input here so we can keep track of the name, and then also a button for submitting it. So I'll say `button` and then we'll say `submit`.

Now, by default, when this form submits, if you're using an action like this, you'll get some form data. So I'll say `formData`, and then we're going to say `const newName = formData.get('name')` and then we're going to update that name. So say `updateUser(newName)` again, which is a method that came from that data access file, and we want to basically update the name where the `userId` is equal to that. And then we'll put `async` in front of this, like that. Let's just style this button real quick as well, and then also style this input.

Okay, let's just type in "testing" and click submit. Notice that the app doesn't, by default, update this. So you actually have to manually refresh it. Now, the way you can tell Next.js to clear its caching mechanisms and refresh the page is simply after you modify some type of data in your database, just call `revalidatePath` and then pass it the path that you want to refresh, basically for your user. So now, if I were to type in "hello world", click submit, notice that that automatically updates. Pretty cool.

But let's take this a step further. Let's say you wanted to basically show a spinner on the button when you try to submit this. How would you do that? Well, typically what I do in my applications is, you can make a new file here called `actions.ts`. You can put `use server` at the top. This is a string that you'll need to tell Next.js that this is going to be basically a bunch of API endpoints you can invoke. And we're going to make a function here. I'll say `export async function updateNameAction(formData: FormData)` like this. It'll take in some form data. And, uh, we can go ahead and delete some stuff. Let's just go ahead and auto-import those things as well. And we're going to keep the `revalidatePath`. But now we don't know what the `userId` is because before we could just pass it in.

So there's two ways to achieve this. One quick way is `updateNameAction.bind(null, userId)`. And then pass it the ID of the user. And if you go up here, you should get a `userId` of a string. And then you can actually use that `userId` in both locations. Let's try that out. I'm going to say "hello world!" click submit, and that still works.

But there's actually a hook built into Next.js that really helps you out here. So I'm going to go ahead and say `const [state, formAction] = useFormState(updateNameAction, initialState)`. Go ahead and auto-import that. And we want to pass in the `updateNameAction` we have. And then I'm also going to pass in a `userId` like this. So now we can actually just call this `formAction` inside this form submission. And then go over here, and this will be `previousState`, and this will take a `userId` of a string. And we can just simply return the same `userId`, and of course, we'll get that out of the `previousState`. So that's a way you can kind of initialize the action with some data. You can also put a hidden input and just submit the `userId` that way, but this is just one approach that works pretty well.

So, unfortunately, you can't use `useFormState` inside of a server action. So what you typically have to do is make a new file called, like, `Form.tsx`. Then we're going to bring in our entire form. But we need to put `'use client'` at the top. If you want to use a custom React hook, make sure you have `'use client'` at the top. And we'll call this just a `Form`. And let's just get rid of some of the stuff that we don't need. Now, we do need the `userId`, so let's just go ahead and bring in a `userId` like this. Now we will need a `userId` in the props. So let's just go ahead and say `userId: string`. And we can start using it in places. We don't need to fetch the user anymore, and we also can't use an `async` keyword inside of a `use client` component.

The last step is we really just want to export the `Form`. We don't want all this extra stuff. So now we have a form that's in a nice, nice file by itself, and we can actually use that down here. So let's just go and import our custom `Form` like this. Make sure we pass it in a `userId`, so `user.id` like this. And then finally, let's clean up that `useFormState` hook because you cannot use hooks in React server components. So let's go ahead and save that. Go back to our app, testing, submit that, and that all works.

Now, the reason we're doing this is because we want to show a little spinner inside the submit button. Luckily, there's another hook called `useFormStatus`. Which, if we were to extract this button into its own special button, so I'll just say `const SubmitButton = () => { ... }`. We can go ahead and return that button like this. We'll call `SubmitButton` here. So now that we pull that button out, we can simply say `const { pending } = useFormStatus()`. And that's going to have a `pending` flag on it. So I can say `pending ? 'Saving...' : 'Save'`.

So let's just try this out. Go ahead and just type in some information, click save, and notice that it shows that quick loading state over here. Now, to kind of add a fake delay, let's just go ahead and say `await sleep(1000)` and then add a promise that just takes some time. And I'll say "testing" again, click submit, and notice that it does say "Saving...".

Now, there's one last issue I want to point out, is that basically when the form submits, sometimes you need to clear out the input. The one way you can achieve this is just add a `ref` to this form. Go ahead and bring in `useRef`, and I'm going to pass that to the form itself. And if the state returns a `message: 'success'`, which we'll default it to nothing for right now, but over here we actually say `message === 'success'`, we can just go ahead and clear out the form. So I'm going to say `formRef.current.reset()`.

Let's test it out. I'll say "hello world", click enter, and notice that it clears out the form once a success message comes back. Now, I will say there are probably tons of other Next.js features you should read about in the docs, but overall, this is the pattern that you're going to be following a lot when you're building out an application, which includes the file-based routing, the path parameters, creating a page, creating `use client` for forms, and having a loader on those forms, using `useFormState`, how to clear out the forms after they're done submitting, how to create an action and have that return some data and revalidate the current path the user is on, and also how to mutate data in your potential database.

So let me know if you like this really short, compact video of me trying to teach something, and I might do it again. Otherwise, have a good day, happy coding!