{"id":25169716,"url":"https://github.com/sofiane-abou-abderrahim/react-nextjs-level-food","last_synced_at":"2026-04-14T03:32:21.870Z","repository":{"id":242938439,"uuid":"810843506","full_name":"sofiane-abou-abderrahim/react-nextjs-level-food","owner":"sofiane-abou-abderrahim","description":null,"archived":false,"fork":false,"pushed_at":"2024-06-12T18:13:10.000Z","size":2748,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-07-29T21:34:42.250Z","etag":null,"topics":["javascript","nextjs","nextjs14","react","react-router","reactjs","sqlite3"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sofiane-abou-abderrahim.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-06-05T13:05:21.000Z","updated_at":"2024-06-12T18:15:06.000Z","dependencies_parsed_at":"2025-07-29T20:34:44.932Z","dependency_job_id":"e7f2355b-0e36-4f3f-8274-18f61024dc38","html_url":"https://github.com/sofiane-abou-abderrahim/react-nextjs-level-food","commit_stats":null,"previous_names":["sofiane-abou-abderrahim/react-nextjs-level-food"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/sofiane-abou-abderrahim/react-nextjs-level-food","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-nextjs-level-food","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-nextjs-level-food/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-nextjs-level-food/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-nextjs-level-food/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sofiane-abou-abderrahim","download_url":"https://codeload.github.com/sofiane-abou-abderrahim/react-nextjs-level-food/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sofiane-abou-abderrahim%2Freact-nextjs-level-food/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31781292,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-14T02:24:21.117Z","status":"ssl_error","status_checked_at":"2026-04-14T02:24:20.627Z","response_time":153,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["javascript","nextjs","nextjs14","react","react-router","reactjs","sqlite3"],"created_at":"2025-02-09T08:36:18.674Z","updated_at":"2026-04-14T03:32:21.856Z","avatar_url":"https://github.com/sofiane-abou-abderrahim.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Foodies Fullstack React Apps With NextJS\n\n## Exercise\n\n1. create three routes\n\n- `/meals`\n- `/meals/share`\n- `community`\n\n2. create a dynamic route\n\n- `/meals/\u003csome slug\u003e`\n\n## 0. Creating a NextJS Project + Exercise\n\n1. go to `https://nextjs.org/` to see the docs\n2. open your terminal and run `npx create-next-app@latest`\n3. run `npm install`\n4. run `npm run dev`\n5. do the exercice tasks\n\n## 1. Exercise: Solution\n\n1. create three new routes: `/meals`, `/meals/share` \u0026 `/community`\n\n   1. create the `/meals` route\n\n   - inside the `app` folder, set up a new route \u0026 a new path segment that can be entered in the URL by adding a `meals` folder\n   - inside of this `app/meals` folder, add a `page.js` file to show something on the screen\n   - in this `meals/page.js` file, export a React functional component named `MealsPage()` for example\n\n   2. create the `/meals/share` route\n\n   - inside of the `app/meals` folder, create a nested `share` folder, again with that path segment you want to have as a folder name\n   - inside of the `meals/share` folder, add a new `page.js` file to be able to visit that page\n   - in `meals/share/page.js`, export a React component function named `ShareMealPage()` for example\n\n   3. create a `/community` route\n\n   - inside of the `app` folder, add a sibling folder to the `meals` folder which you name `community`\n   - inside of it, add a `page.js` file\n   - in this file, export a React component function named `CommunityPage()` for example\n\n2. add some links to allow users to navigate between those routes\n\n   1. start with the home page\n\n   - in `app/page.js`, add a `\u003cp\u003e` that should display a `\u003cLink\u003e` to the `MealsPage`\n   - add another link to the `ShareMealPage`\n   - add a last link to the `CommunityPage`\n\n3. create a dynamic route that allows users to go to `/meals/\u003csome slug\u003e` (some dynamic segment/some identifier of the meal)\n\n   1. in `app/meals`, add another `[mealSlug]` subfolder which is a sibling of the `share` folder\n   2. inside of it, add a `page.js` file\n   3. in it, export a component function named `MealDetailsPage()`\n\n## 2. Adding a Custom Component To A Layout\n\nAdd a header, navigation \u0026 logo to this website\n\n1.  add a `main-header.js` file inside of a `components` folder outside the `app` folder\n2.  in this file, export a React component function named `MainHeader()`\n3.  in `app/layout.js`, output this `\u003cMainHeader\u003e`\n\n## 3. Styling NextJS Project: Your Options \u0026 Using CSS Modules\n\n1. importing `globals.css` files\n2. Tailwind CSS\n3. NextJS CSS modules\n   1. create a new `main-header.module.css` file next to the `main-header.js` file\n   2. in this file, set up the CSS classes \u0026 styles for the main header\n   3. import this file into this `main-header.js` file, like this `import classes from './main-header.module.css';`\n      1. to make those CSS classes available in this file\n      2. and scope those classes to this `MainHeader` component\n   4. use this `classes` to access certain properties (the classes in `main-header.module.css`)\n\n## 4. Optimizing Images with the NextJS Image Component\n\nin NextJS, you have a special built-in `\u003cImage\u003e` component which outputs images in a more optimized way than the `\u003cimg\u003e` element,\nfor example,\n\n- by lazy loading images under the hood\n- simplify the process of setting up responsive images\n- and so on\n\n1. in `main-header.js`, replace the default `\u003cimg\u003e` element with the `\u003cImage\u003e` element imported from `next/image`\n2. set the `src` to the overall object `{logoImg}`, and not to the `.src` property value\n3. as you can learned on the browser console, you should add the `priority` property to this image since it will always be visible when this page loads\n\n## 5. Using More Custom Components\n\nOutsource the header background from `app/layout.js` into a separate component\n\n1. in the `components` folder, add a `main-header-background.js` file\n2. in there, export a component named `MainHeaderBackground()`\n3. grab the `\u003cdiv\u003e` from `layout.js` \u0026 paste it in `main-header-background.js`\n4. add a `main-header-background.module.css`\n5. cut the `.header-background` class \u0026 the `svg` selector from `app\\globals.css` \u0026 paste in `main-header-background.module.css`\n6. in `main-header-background.js`, import the classes from `main-header-background.module.css` \u0026 use them\n7. in `components\\main-header.js`, output the `\u003cMainHeaderBackground\u003e`\n8. in the `components` folder, group all the related header files together into a sub folder named `main-header`\n\n## 6. Populating The Starting Page Content\n\nNow that you finished the main navigation header, let's continue working on this main page content (starting page content)\n\n1. in `app/page.js`, set up some nested page specific `\u003cheader\u003e` \u0026 below it the `\u003cmain\u003e` section of this page\n2. next to this file, add a new `page.module.css` to apply some styles to it\n\n## 7. Preparing an Image Slideshow\n\nAdd a little image slideshow which automatically goes through some food images that should show up\n\n1. in the `components` folder, add a new subfolder called `images` \u0026 in there, add a `image-slideshow.js` file\n2. in this file, build a new component named `ImageSlideshow`\n3. next to it, add a new `image-slideshow.module.css` to apply some styles to it\n4. in `app/page.js`, output the `\u003cImageSlideshow\u003e` component\n\n## 8. React Server Components vs Client Components - When To Use What\n\nIn order to make this `ImageSlideshow` component work\n\n1. in `components\\images\\image-slideshow.js`, add the `'use client';` directive at the top of this file\n2. with that you can now output the `\u003cImageSlideshow\u003e` component in `app\\page.js`\n\n## 9. Using Client Components Efficiently\n\nNow that you finished this starting page, let's continue working on this Community page\n\n1. populate `app\\community\\page.js` with some dummy content\n2. next to it, add a `app/community/page.module.css` file to apply some styles to it\n3. in `components\\main-header\\main-header.js`, highlight the active navigation link\n   1. get hold of the currently active path with help of the NextJS `usePathname()` hook \u0026 store it in a `path` constant\n   2. use this `path` constant in the `\u003cLink\u003e` to add the `active` class conditionally\n   3. add the `'use client';` directive at the top of this file\n4. add a new `nav-link.js` \u0026 export a `NavLink` component function \u0026 add a `nav-link.module.css` file next to it\n   1. in `nav-link.js`, paste the `\u003cLink\u003e` cut from `components\\main-header\\main-header.js`\n   2. make the `\u003cNavLink\u003e` component function configurable\n   3. convert it into a client component by adding the `'use client';` directive at the top of this file\n   4. cut the link \u0026 the class related CSS styles from `main-header.module.css` \u0026 paste them in `nav-link.module.css`\n   5. add `import classes from './nav-link.module.css'`\n5. back in `components\\main-header\\main-header.js`, make sure that this component is not converted into a client side component by\n   1. getting rid of `'use client';` \u0026 `usePathname()`\n   2. outputting this newly added `\u003cNavLink\u003e` component\n\n## 10. Outputting Meals Data \u0026 Images With Unknown Dimensions\n\nNow that you worked on the starting page, the community page \u0026 improved the header,\nit's time to work on that meals page \u0026 output some meals there\nbefore you then thereafter will make sure that users can also share meals\n\n1. in `app\\meals\\page.js`, output a bunch of meals which you will soon store in a database\n   1. set up the base markup/structure of this page\n   2. next to this file, add a new `page.module.css` for applying some styles to it\n2. add a separate component for outputting the meals\n   1. in the `components` folder, add a `meals` folder\n   2. in there, add a `meals-grid.js` file for outpuuting a bunch of meal items in a grid\n   3. next to it, add a `meals-grid.module.css` for applying some styles to it\n   4. in the same `meals` folder, add another file named `meal-item.js`\n   5. next to it, add a `meal-item.module.css` for applying some styles to it\n   6. output the `\u003cMealItem\u003e` component in `meals-grid.js`\n3. in `meals/page.js`, output the `\u003cMealsGrid\u003e` component (you have no data yet, so you won't see anything on the screen)\n\n## 11. Setting Up A SQLite Database\n\nIn order for this `\u003cMealsGrid\u003e` component in `meals/page.js` to be useful \u0026 to see something on the screen, you need meals\nAnd for that you can set up a basic database that would store some dummy meals \u0026 that would later also store meals shared by users\n\n1. in your terminal, run `npm install better-sqlite3` which is a package that will allow you to work with a sqlite database\n2. add a `initdb.js` file in your root project directory to create a database \u0026 populate it with some dummy data\n3. in your terminal, run `node initdb.js` to have some dummy meals available \u0026 have a `meals.db` file created (the sqlite database)\n\n## 12. Fetching Data By Leveraging NextJS \u0026 Fullstack Capabilities\n\nWhen it comes to loading data in a NextJS application, you got a couple of different options:\n\n- you could fetch the data as you would do it in any Vanilla React application, for example with the `useEffect()` hook \u0026 in there the `fetch()` to send a request to a backend\n- in a NextJS application, you already have a backend \u0026 since the `MealsPage` component, by default, runs on the server, you can directly reach out to the database\n\n1. in your root project folder, add a new `lib` folder \u0026 in there, add a new `meals.js`\n2. in `lib/meals.js`, write the code that reaches out the database\n3. in `app\\meals\\page.js`, call `getMeals()` inside the `MealsPage()` component function imported from `lib\\meals.js` to get back your meals\n4. with that done, pass these `{meals}` as a value to the `meals` prop in the `\u003cMealsGrid\u003e` component to render the data fetched from the database \u0026 the images from the `public` folder\n\n## 13. Adding A Loading Page\n\nIt would be nice if you had some loading indicator whilst the page is loading when you visit it\n\n1. next to `app\\page.js`, add a new `loading.js` page\n   - which just like `page.js` \u0026 `layout.js` is a reserved file name\n   - this file will become active if the page next to it or any nested page or layout is loading data\n   - and in that case that `loading.js` content is shown as a fallback until the data is there\n2. next to `loading.js`, add a `loading.module.css` file for applying some styles to it\n\n## 14. Using Suspense \u0026 Streamed Responses For Granular Loading State Management\n\nThe loading text basically occupies the entire screen\nHowever, the header doesn't display and it doesn't depend on any loaded data at all\nSo, show this header instantly \u0026 only show the loading text whilst you're waiting for the meals to be fetched\n\n1. replace `loading.js` by `loading-out.js` so that it doesn't have any special purpose anymore (so you won't use it anymore)\n2. move the `loading-out.js` \u0026 the `loading.module.css` file into the `meals` folder (it was the place where you should use at first)\n3. now, go to `app\\meals\\page.js` because NextJS gives a more granular way of handling loading states\n   1. basically, you can go to the place where you have some operation that might take a bit longer, like here in `\u003cMealsGrid\u003e`\n   2. you can then create a separate component (here you can do it in `app\\meals\\page.js` since it would be a component that is closely connected to this `MealsPage`)\n   3. name this component `Meals`\n   4. this is now this component that will fetch the data\n   5. this component will now return this `\u003cMealsGrid\u003e`\n   6. and now the `\u003cMeals\u003e` can be output in the `MealsPage()` component function\n      - the advantage is that now you outsourced the data fetching part into a separate component\n   7. so you can now wrap this `\u003cMeals\u003e` component with the `\u003cSuspenses\u003e` component that is built into React\n\n## 15. Handling Errors\n\nBesides `page.js`, `layout.js` \u0026 `loading.js`, you can also add an `error.js`, which is another reserved file name\nwhich would handle potential errors that might be generated by your pages \u0026 components,\nfor example, if loading some data fails\n\n1. to simulate this, let's go `lib/meals.js` \u0026 throw an error\n2. add some error handling, by adding `app\\meals\\error.js`\n3. this file must be a client component, so add the `use client` directive at the top of it\n4. go back to `lib/meals.js` \u0026 comment out that error\n\n## 16. Handling \"Not Found\" States\n\nThere is another kind of error that could occur when a user enters an invalid URL\nNextJS has a default 404 page to handle that\nYou can change it, if you want to\n\n1. add a `not-found.js` file which is a reserved file name to the root of your project for example\n2. in it, export a `NotFound()` component function\n\n## 17. Loading \u0026 Rendering Meal Details via Dynamic Routes \u0026 Route Parameters\n\n1. go to `app\\meals\\[mealSlug]\\page.js` \u0026 set up the base markup/structure of this page which contains a `header` \u0026 a `main` section\n   1. the `header` section outputs the title, the creator's name \u0026 a summary\n   2. the `main` section outputs the instructions that are stored for every meal as HTML code by targetting the `dangerouslySetInnerHTML` prop to avoid cross site scripting attacks\n2. next to it, add a `page.module.css` for applying some styles to it\n3. in order to have some data to output in your `MealDetailsPage`, go back to `lib\\meals.js` \u0026 export a new `getMeal()` function which accepts a `slug` as a parameter\n4. go back to `app\\meals\\[mealSlug]\\page.js` \u0026 call `getMeal()` to store every meal in a `meal` constant\n5. `getMeal()` needs that `slug` which is that part that's encoded in the URL that is called with help of\n   1. this `mealSlug` placeholder which you get via the special `{params}` prop that's available in every `page.js` file\n   2. which contains an object as a value where any dynamic path segment that's configured for this route will be stored as a key/value pair\n   3. so `mealSlug` will be used as a key\n   4. and the actual value encoded in the URL will be used as a value for that key\n   5. and it is that value that you need to get to look for that value in the database\n   6. so, pass `params.mealSlug` to `getMeal()` \u0026 that would be that identifier that allows you to fetch a meal from the database\n6. use this `meal` constant to populate the JSX code with the meal data\n7. use `{meal.instructions.replace()}` \u0026 a regular expression to output the line breaks\n\n## 18. Throwing Not Found Errors For Individual Meals\n\nIf a user tries to look for a meal that doesn't exist, you get \"An error occured\" page,\nbecause you try to get the `instructions` on undefined, because you weren't able to load a meal\nBut, it's not actually an error that occurs, instead the user just didn't find the requested meal\n\n1. in `app\\meals\\[mealSlug]\\page.js`, check if `meal` is undefined \u0026 then show the closest `not-found.js` or `error.js` page available in this project\n2. to trigger that, call the special `notFound` function provided by NextJS and imported by `next/navigation`\n3. add a new `not-found.js` file in `app\\meals` so that you can output a more customized not found error\n\n## 19. Getting Started with the \"Share Meal\" Form\n\n1. in `app\\meals\\share\\page.js`, set up a form to add our own meals\n2. next to it, add a `page.module.css` for applying some styles to it\n\n## 20. Getting Started with a Custom Image Picker Input Component\n\n1. in the `components\\meals` folder, add a new `image-picker.js` file\n   1. in it, export a `ImagePicker()` component function\n   2. in this function, output some markup \u0026 handle the picking process\n2. next to it, add a `image-picker.module.css` for applying some styles to it\n3. back in `app\\meals\\share\\page.js`, use this `\u003cImagePicker\u003e` component\n4. make this image picker a bit more fancy by replacing the default button with your own nice button\n\n## 21. Adding an Image Preview to the Picker\n\nTo show a preview of the picked image, you need to handle the event that an image was picked\nand then store some state so that you can update the UI \u0026 show up a preview as soon as you have an image\n\n1. in `components\\meals\\image-picker.js`, add a `pickedImage` state \u0026 `setPickedImage` updating function\n2. add a second event handler function named for example `handleImageChange()`\n3. which should be trigger whenever the `\u003cinput\u003e` has a new value, so add to it `onChange={handleImageChange}`\n4. configure that `handleImageChange()` function to get hold of that `event` with help of `event.target.files[0]`\n5. to show this file as a preview, convert it into a data URL which is a value that can be used as an input/source for an image element with help of\n   1. the `new FileReader()` class built into JavaScript\n   2. and the `readAsDataURL()` method, by passing the `file` to it like this `fileReader.readAsDataURL(file);`\n   3. get hold of that data URL that's been generated by assigning a value to the `onload()` property of this `fileReader` object\n   4. you get access to the generated URL by accessing `fileReader.result` which will be that generated URL\n   5. store this generated URL in your state like this `setPickedImage(fileReader.result);`\n6. use this `pickedImage` state to show a preview\n\n## 22. Introducing \u0026 Using Server Actions for Handling Form Submissions\n\nWhen it comes to handling form submissions:\n\n- you could do that as you do it with most React projects\n  - by adding the `onSubmit` prop to the `form` element\n  - \u0026 defining a function that should be executed when the form is submitted\n  - then, in there, prevent the browser default, manually collect all the data \u0026 send it to a backend\n- in NextJs, you already have a backend, that's why it gives you a more powerful \u0026 convenient pattern:\n\n1. in `app\\meals\\share\\page.js`, create a function which you could call `shareMeal`\n2. inside of it, add the special `use server` directive which create a so called server action which is a function that's guaranteed to execute on the server and only there\n3. you must add `async` in front of the `shareMeal()` function to really turn this `use server` into a server action\n4. this feature exists (in React, not just in NextJS) so that you can assign this server action as a value for the `action` prop on a function\n   - so here `action={shareMeal}`, you assign `shareMeal` as a value on the `action` prop of the `form`\n   - this pattern will ensure that when this form is submitted, NextJS will behind the scenes create a request and send it to this NextJS server that's serving the website, so that `shareMeal()` function gets triggered \u0026 you can then handle the form submission there, but on the server, not in the client\n5. this `shareMeal()` function automatically receives the `formData` that was submitted, so pass `formData` to it as a parameter\n6. then, use that `formData` object to handle the submitted data, for example create a `meal` object by extracting meal data from that `formData`\n7. the next step is to store this `meal` data in a database\n   - though, the image should be stored on the file system and then a path of the image should be stored in a database\n8. but, before that just console log the `meal` to see some output on the server side in your terminal for now\n\n## 23. Storing Server Actions in Separate Files\n\nThere also is another way to add a server action:\n\n- you can add it in a component like you did before, but this would only work if the component in which you are adding it is not a client component\n- you might not want to have your server-side form submission handling logic in the same file as your JSX code\n\n1. store your server action in a separate newly added `actions.js` file in the `lib`\n2. at the top of this file you have to add the `use server` directive\n3. back to `app\\meals\\share\\page.js`, cut the `shareMeal()` function\n4. paste it in `lib\\actions.js` \u0026 export it\n5. back to `app\\meals\\share\\page.js`, import it \u0026 use it\n6. now you would be able to convert this component into a client component if you needed to\n\n## 24. Creating a Slug \u0026 Sanitizing User Input for XSS Protection\n\nlet's now work on storing that data\n\n1. in `lib\\meals.js`, add a new `saveMeal()` function which expect a `meal` object as an input for saving a meal\n2. inside this function,\n   1. generate a slug based on the title, because you want to store a slug in your database for every meal\n      - in your terminal, run `npm install slugify`\n      - create a new `slug` by calling `slugify(meal.title, {lower: true})`\n   2. sanitize the content sent by the user to protect your site against cross site scripting attack\n      - in your terminal, run `npm install xss`\n      - remove any harmful content from the `instructions` by calling `xss(meal.instructions)`\n3. in `lib\\meals.js`, import these 2 packages\n4. with all that done, you prepared all the data except for the image\n\n## 25. Storing Uploaded Images \u0026 Storing Data in the Database\n\nThe image should be stored on the file system, not in the database\nso, you can store the uploaded files in the `public` folder\n\n1. in `lib\\meals.js`, get the `extension` of the uploaded image\n2. generate a unique `fileName`\n3. write the `fileName` in a file in the `public` folder with help of the file system `fs` API provided by NodeJS\n   - import `fs` from `node:fs`\n   - use `fs` to call `createWriteStream()` to which you should pass a path\n   - from it, get a `stream` which you can use to write to that image in that path with help of `write()` which wants a `chunk` \u0026 a function\n     - in order to get the chunk, you should convert the image into a `bufferedImage` with help of the `arrayBuffer()` method\n     - the function takes an `error` argument \u0026 should throw an error message in case there is an error\n4. override the `meal.image` that stored in your `meal` object (in `lib\\actions.js`) with the `/images/${fileName}` path to the image where we stored it\n5. with that you finished the preparation for this `meal` object \u0026 now you can save it in the database with help of `db.prepare().run()`\n6. go back to your server action to `lib\\actions.js` \u0026 call `saveMeal()` \u0026 redirect()\n\n## 26. Managing the Form Submission Status with useFormStatus\n\nFurther enhance the user experience, because now, when submitting the form, it takes some time before getting redirected\nSo, it would be nice to get some feedback as a user whilst the data is being submitted to see that the request is on its way\nfor example, you could update the \"Share Meal\" button once it's clicked\n\n1. you can use the `useFormStatus()` hook provided by React (but again, this feature really only works when using NextJS)\n\n   1. in `app\\meals\\share\\page.js`, import `{useFormStatus}` from `react-dom`\n   2. use it to get a `status` object with, for example, a `pending` property\n   3. to remove the error on `http://localhost:3000/meals/share`, you could add `use client` at the top of `app\\meals\\share\\page.js`\n\n      - however, you might not want to turn this entire page into a client component just because of updating conditionally this button\n      - in addition, the `useFormStatus()` hook will only give you the submission status of a form if it's inside of that form for which it should give you the status, so, it wouldn't work anyway\n\n2. so, remove this `useFormStatus()` hook from `app\\meals\\share\\page.js` \u0026 instead add a new `meals-form-submit.js` component in `components\\meals`\n\n   1. in this file, export a `MealsFormSubmit()` component function\n   2. import `{useFormStatus}` from `react-dom`\n   3. add the `use client` directive at the top of this file\n   4. inside of `MealsFormSubmit()`, get the `status` by calling `useFormStatus()`\n   5. use the object destructuring to pull out some properties from this `status` object, like in your case the `pending` property\n   6. return a `\u003cbutton\u003e` that conditionally outputs \"Submitting...\" or \"Share Meal\"\n\n3. go back to `app\\meals\\share\\page.js` \u0026 output the `\u003cMealsFormSubmit\u003e` component instead of the vanilla `\u003cbutton\u003e`\n\n## 27. Adding Server-Side Input Validation\n\nOne other thing you should typically do when handling user data \u0026 form submissions is validate the values you're getting\nAnd at the moment, you're not performing any validation at all in your server action in `lib\\actions.js`\n\n1. in `lib\\actions.js`, add a helper function named `isInvalidText()` for example\n2. use this helper function in an if statement to check each input, the email \u0026 the image \u0026 throw an error\n3. in `app\\meals\\share`, add a specific `error.js` file\n\n## 28. Working with Server Action Responses \u0026 useFormState\n\nHandle validation errors in a more elegant way so that the user doesn't lose his data\n\n1. in `lib\\actions.js`, instead of throwing an error, return a response object\n   - wich has to be a serializable object without any methods for example\n   - in this object, add a `message` field\n2. use that response in `app\\meals\\share\\page.js` where you trigger that server action with help of another hook named `useFormState()` hook which is provided by React DOM\n   - it's similar as `useState()` because it's responsible for managing the state of this component which uses a form that will be submitted with help of server actions\n3. in `lib\\actions.js`, you need to tweak the `shareMeal` action though because it should now accept 2 parameters\n   - the first one is the `prevState`\n   - the second one is still the submitted data `formData`\n4. in `app\\meals\\share\\page.js`, add the `use client` directive at the top of the file\n\n## 29. Building For Production \u0026 Understanding NextJS Caching\n\n1. in your terminal, run `npm run build`\n2. run `npm start`\n\nNextJS performs some pretty agressive caching\nand it prerenders all the pages of your app that can be pre-generated (basically, all non-dynamic pages)\nfor example, it pregenerates the `meals` page (`app\\meals\\page.js`)\nthat's why:\n\n- now, in production, when adding a new meal, the meal doesn't render\n- the delay added in `lib\\meals.js` is also missing in production\n\nSo, the downside of this approach, that it never re-fetches the meals (`const meals = await getMeals()`) because the `Meals()` function never executes again\n\n3. To prove that, in `app\\meals\\page.js`, console log \"Fetching Meals\"\n4. run again `npm run build` \u0026 `npm start`\n5. if you reload the meals page, you don't see \"Fetching meals\" in your terminal because those pages were pregenerated \u0026 cached\n\n## 30. Triggering Cache Revalidations\n\nTo fix the problem of NextJS caching to aggressively, you need to tell NextJS to throw away its cache or part of its cache whenever you add a new meal\nFor that, there is a built-in function provided by NextJS named `revalidatePath()`\n\n1. in `lib\\actions.js`, right after saving a meal and befor redirecting, use this `revalidatePath('/meals')` function imported from `next/cache`\n2. in your terminal, run `npm run build` \u0026 `npm start`\n3. now, the image is missing, but the newly added meal is rendered on the meals page\n4. in addition, in your terminal you get these \"Fetching meals\" logs back\n5. you also have this error message `⨯ The requested resource isn't a valid image for /images/not-cached.jpg received text/html; charset=utf-8` in your terminal\n\n## 31. Don't Store Files Locally On The Filesystem!\n\nLet's focus on that missing image\nThe image is missing because the images are stored in the `public/images` folder which is only available in development mode\nThat's why in NextJS docs https://nextjs.org/docs/pages/building-your-application/optimizing/static-assets, it says: `We recommend using a third-party service like Vercel Blob for persistent file storage.`\nYou can aslos useAWS S3 or Cloudinary, for example\n\n## 32. Bonus: Storing Uploaded Images In The Cloud (AWS S3)\n\n## 33. Adding Static Metadata\n\nSet some specific metada for some of your pages\n\n1. in `app\\meals\\page.js`, export a `metadata` constant\n2. set a `title` \u0026 a `description` to it\n3. in your terminal run `npm run build` \u0026 `npm start`\n\n## 34. Adding Dynamic Metadata\n\nFor dynamic pages, instead of exporting a `metadata` constant, export an `async function generateMetadata()` which is also a reserved name\n\n1. in `app\\meals\\[mealSlug]\\page.js`, export an async function named `generateMetadata()`\n2. pass to it the `params` prop as an argument to get the meal slug for which the metadata should be generated\n3. inside of that function, return an object with a `title` \u0026 a `description` field\n4. if you enter an invalid slug in the URL, you get an error instead of the \"NotFound\" page, so to fix this, inside of that function, add a check where you call the `notFound()` if the `meal` is undefined\n5. in your terminal, run `npm run build` \u0026 `npm start`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofiane-abou-abderrahim%2Freact-nextjs-level-food","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsofiane-abou-abderrahim%2Freact-nextjs-level-food","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsofiane-abou-abderrahim%2Freact-nextjs-level-food/lists"}