This shows you the differences between two versions of the page.
se:labs:04 [2022/11/03 20:37] gabriel.nicolae3103 [JavaScript-based Form Validation] |
se:labs:04 [2024/11/01 19:06] (current) cosmin.dumitrache [Lab 04 - Next.js: SSR and SSG] |
||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== Lab 04 - React: Forms and APIs ====== | + | ====== Lab 04 - Backend ====== |
- | ===== Introduction ===== | + | ===== Single Page Applications (SPAs) ===== |
- | In this lab we are going to get comfortable working with forms and public APIs. | + | Classic web pages usually render visual elements on the screen using **HTML** (to structure the visual elements) and **CSS** (to style them), while utilizing JavaScript just to make that page interactive (e.g. trigger an action when the user clicks a button, perform error handling for forms, display additional information on hover, etc.). |
- | An HTML form element represents a document section containing interactive controls for submitting information. | + | In contrast, **Single Page Applications** or **SPA**s rely heavily on **JavaScript** for almost everything, including rendering the entire user interface (UI). That is also the case for almost every web development framework (also simply called JavaScript frameworks) out there, including Angular, Vue, and React. **React** is a telling example, as it uses JavaScript for writing every part of the component, including the template (JSX) and the styling (more on that in future tutorials). When you build a React application to make it ready for production, the JSX template, the styling of the components, and the JS logic are transformed using a compiler (usually **Babel**) and bundled together into pure JS code and optimized using a dedicated build tool (usually **Webpack**). The resulting JS resources together with minimal HTML and CSS boilerplate are then served to the browser when the latter performs a GET request. |
- | In the example below we created a simple form containing only one input representing a Name. | + | |
- | <code html> | + | {{:se:labs:se-react-spa.png?700|Single Page Applications}} |
- | <form> | + | |
- | <label> | + | |
- | Name: | + | |
- | <input type="text" name="name" /> | + | |
- | </label> | + | |
- | <input type="submit" value="Submit" /> | + | |
- | </form> | + | |
- | </code> | + | |
- | ===== Controlled Components ===== | + | An **advantage** of this approach is that the server's response to a client request comprises minimal HTML and CSS code and some JavaScript resources that are usually obfuscated and heavily minified. That leads to faster response times from the server and a pleasing user experience (UX), as the visual elements start to be rendered dynamically as soon as the browser starts executing the JavaScript received from the server. Moreover, when the user navigates through the app, a few requests are further sent to the server as the majority of new visual elements are displayed in the browser by executing even more JavaScript written in the same resources that were initially fetched when the page was initialized. |
- | In React, Controlled Components are those in which form’s data is handled by the component’s state. It takes its current value through props and makes changes through callbacks like onClick,onChange, etc. A parent component manages its own state and passes the new values as props to the controlled component. | + | The big **disadvantage** however, is that crawlers don't like SPAs. That means that when Google's bots visit your page to index it and then rank it for search results, they usually don't like to wait for the loading and execution of JS code so they can understand what visual elements are in it. And even if the crawlers have become increasingly more performant and JS-tolerant, there is still a problem with semantics. JavaScript frameworks were not designed with semantics in mind. That means that the HTML elements that are dynamically generated at runtime usually don't respect the best practices (e.g. use paragraphs for text content, use headers in hierarchical order, include [[https://www.w3schools.com/html/html5_semantic_elements.asp|semantic elements]], etc.). That is a big red flag for SEO and solving it just by using React is pretty difficult. |
- | In the form elements are either the typed ones like textarea, input or the selected one like radio buttons or checkboxes. Whenever there is any change made it is updated accordingly through some functions that update the state as well. | + | ===== Pre-Rendering (SSR and SSG) ===== |
- | <code javascript> | + | To solve the discussed issues, we can use **pre-rendering**. That is generating HTML and CSS code in advance, for each page instead of relying solely on the client-side execution of JavaScript. As a result, the amount of JS code executed by the browser is heavily reduced as it is used only for making the page fully interactive. This process is called **hydration** and a great tool for doing this in React is **Next.js**. |
- | export default function Form() { | + | |
- | const [name, setName] = React.useState(''); | + | |
- | function handleChange(event) { | + | {{:se:labs:se-react-prerendering.png?700|}} |
- | setName(name => event.target.value); | + | |
- | } | + | |
- | function handleSubmit(event) { | + | There are usually **2 types of pre-rending**, both being supported by Next.js. |
- | alert('A name was submitted: ' + name); | + | |
- | event.preventDefault(); | + | |
- | } | + | |
- | return ( | + | === 1. Server-Side Rendering (SSR) === |
- | <form onSubmit={handleSubmit}> | + | |
- | <label> | + | |
- | Name: | + | |
- | <input type="text" value={name} onChange={handleChange} /> | + | |
- | </label> | + | |
- | <button type="submit" value="Submit" /> | + | |
- | </form> | + | |
- | ); | + | |
- | } | + | |
- | </code> | + | |
- | In most cases it is recommended to use Controlled Components to implement forms. | + | In this case, the HTML and CSS code is **generated on each request**. This is an SEO-friendly approach that is hybrid in nature, as it allows the application to serve different content in a context-dependent fashion while minimizing the amount of JavaSript code that gets executed on the client. Hence, **SSR** is perfect for scenarios where real-time interactivity (e.g. a live map, live stats display, counter, etc.) is not required, but some parts of the page cannot be determined at build time, but on each HTTP request. |
- | === preventDefault() === | + | {{:se:labs:se-next-ssr.png?700|}} |
- | By default, the browser will refresh the page when a form submission event is triggered. We generally want to avoid this in React.js applications because it would cause us to lose our state. | ||
- | To prevent the default browser behavior, we have to use the preventDefault() method on the event object. | + | === 2. Static Site Generation (SSG) === |
- | <code javascript> | + | **SSG** goes one step further and pre-renders the styled HTML at build time. That means that the same content will be returned for every subsequent HTTP request and data on that page will not change whatsoever. This approach is suitable for landing pages, articles, blog posts, or other web pages that solely rely on static content. This pre-rendering technique is **highly recommended** as it offers huge SEO benefits, but it can also be leveraged by Content Delivery Networks (CDNs) or other caching techniques (e.g. [[https://www.cloudflare.com/en-gb/learning/cdn/what-is-a-cdn/|Cloudflare]]). |
- | function handleSubmit(event) { | + | |
- | alert('A name was submitted: ' + name); | + | |
- | event.preventDefault(); | + | |
- | } | + | |
- | + | ||
- | </code> | + | |
+ | {{:se:labs:se-next-ssg.png?700|}} | ||
+ | ===== Server/Client Components ===== | ||
+ | Next.js 15 goes one step further and instead of looking at the pages as SSG/SSR it instead using new react concepts as Server Components and Client Components. In Next.js 15, all your components are by default server components, which means Next.js will try to populate them when rendering the page before sending it to the client's browser. Since all components are by default server components and are rendered on the server, there is no hydration step necessary (it still takes place when possible) and your pages will work even with javascript disabled. In these situations the data fetching happens on the server. To find out more about the advantages of server components read the [[https://nextjs.org/docs/app/building-your-application/rendering/server-components|Server Components]] page. | ||
- | ===== Text Area ===== | + | On the other hand if your page needs some level of interactivity using javascript/react on the client side (and you need to make use of methods like useState/useEffect) you will need to upgrade your component to a [[https://nextjs.org/docs/app/building-your-application/rendering/client-components|Client Component]]. In these components you are allowed to import from the react module, in server components you are not. |
- | A <textarea> element in HTML: | + | It is always recommended to use serverside components unless you need actual interactive components on the client side. For example imagine a blog written in Nextjs with react. Almost all pages will be static with (latest articles page, single article page with comment section, etc.), except for a few widgets relevant to the logged in user. In this situation we would design all our pages as static components, rendered fully on the server with links sending to the user to another page. For example, if the hydration step takes place, nextjs will considered the page "enhanced" and request a smaller amount of data to populate the new page (of the article, when coming form the home page), on the other hand if the hydration did not take place (due to a variety of factors like: js disabled, low internet speed and js did not load yet) clicking the link will redirect the browser to the article's URL which in change will request a new full page render from the server. This way, server components end up supporting both situations when the page hydrates or not. That is why the default in nextjs 15 is server components. |
- | <code html> | + | For a more in-depth comparison between server and client components please read [[https://nextjs.org/docs/app/building-your-application/rendering/composition-patterns|Server and Client Composition Patterns]] |
- | <textarea> | + | |
- | DSS rules! | + | |
- | </textarea> | + | |
- | </code> | + | |
- | + | ||
- | In React, a <textarea> uses a value attribute instead. This way, a form using a <textarea> can be written very similarly to a form that uses a single-line input: | ||
- | <code html> | + | ===== Next.js Routing ===== |
- | <form onSubmit={handleSubmit}> | + | |
- | <label> | + | |
- | Feedback Laborator: | + | |
- | <textarea value={value} onChange={handleChange} /> | + | |
- | </label> | + | |
- | <button type="submit" value="Submit" /> | + | |
- | </form> | + | |
- | + | ||
- | </code> | + | |
+ | Next.js has an opinionated way of handling the application routes. In order to define new website pages you need to create a page.tsx file for each in a int app folder or in a sub folder representing the path you want to implement | ||
- | ===== Select ===== | + | <code javascript> |
- | + | * /app/page.tsx //- will define the hopage | |
- | In HTML, <select> creates a drop-down list. For example, this HTML creates a drop-down list of our previous labs: | + | * /app/about/page.tsx //- will define the page located at domain.com/about |
- | + | ||
- | <code html> | + | |
- | export default function Form() { | + | |
- | const [value, setValue] = React.useState('lab3') | + | |
- | + | ||
- | + | ||
- | return ( | + | |
- | <select value={value} onChange={handleChange}> | + | |
- | <option value="lab1">Lab 1</option> | + | |
- | <option value="lab2">Lab 2</option> | + | |
- | <option value="lab3">Lab 3</option> | + | |
- | <option value="lab4">Lab 4</option> | + | |
- | </select> | + | |
- | ); | + | |
- | } | + | |
</code> | </code> | ||
- | Note that the "Lab 3" option is initially selected, because of its initial state. | + | You can find more information in the [[https://nextjs.org/docs/app/building-your-application/routing/pages|Pages section]] |
- | ===== Handling Multiple Inputs ===== | + | ===== Dynamic Routes ===== |
- | When you need to handle multiple controlled input elements, you can add a name attribute to each element and let the handler function choose what to do based on the value of event.target.name. | + | Next.js also supports [[https://nextjs.org/docs/app/building-your-application/routing/dynamic-routes|**dynamic routes**]] which are useful when the route depends on a URI parameter. Here are some examples of URLs where this might come in handy: |
<code javascript> | <code javascript> | ||
- | const MyComponent = () => { | + | * /blog/:post-id |
- | const [inputs, setInputs] = useState({}); | + | * /profile/:user-id |
- | const handleChange = e => setInputs(prevState => ({ ...prevState, [e.target.name]: e.target.value })); | + | * /videos/:video-id/statistics |
+ | * /questions/* | ||
+ | </code> | ||
- | return ( | + | In these cases, square brackets are used in the naming of files or directories to indicate that the route is dynamic: |
- | <div> | + | |
- | <input name="field1" value={inputs.field1 || ''} onChange={handleChange} /> | + | |
- | <input name="field2" value={inputs.field2 || ''} onChange={handleChange} /> | + | |
- | </div> | + | |
- | ) | + | |
- | } | + | |
- | </code> | + | |
+ | <code javascript> | ||
+ | * /app/article/[article-id]/page.tsx | ||
+ | * /app/user/[user-id]/page.tsx | ||
+ | * /app/videos/[video-id]/statistics.tsx | ||
+ | * /app/questions/[...all].jsx | ||
+ | </code> | ||
- | ===== Form Validation ===== | + | <note tip>You can find a compelling example of how dynamic route structuring works [[https://github.com/vercel/next.js/tree/canary/examples/dynamic-routing|here]].</note> |
- | Form Validation is the process used to check if the information provided by the user is correct or not (eg: if an email containts '@'). | + | ===== Page Linking ===== |
- | There are two types of validation: | + | |
- | * Client Side: Validation is done in the browser | + | |
- | * Server Side: Validation is done on the server | + | |
- | Client-side validation is further categorized as: | + | To link pages together, Next.js provides you with a dedicated component called **//Link//**, which works similarly to the classic anchor tag. |
- | * Built-in: Uses HTML-based attributes like required, type, minLength, maxLength, pattern, etc. | + | |
- | * JavaScript-based: Validation that's coded with JavaScript. | + | |
- | Validation HTML-based attributes: | + | <code javascript> |
- | * required: the fields with this attribute must be filled. | + | import Link from 'next/link'; |
- | * type: i.e a number, email address, string, etc. | + | |
- | * minLength: minimum length for the text data string. | + | |
- | * maxLength: maximum length for the text data string. | + | |
- | Example: | + | function Home({ otherBlogPosts }) { |
+ | return ( | ||
+ | <ul> | ||
+ | <li> | ||
+ | <Link href="/">Home</Link> | ||
+ | </li> | ||
+ | <li> | ||
+ | <Link href="/blog">Thoughts</Link> | ||
+ | </li> | ||
+ | <li> | ||
+ | <Link href="/blog/first-post">How the story beginned</Link> | ||
+ | </li> | ||
+ | {otherBlogPosts.map((post) => ( | ||
+ | <li key={post.id}> | ||
+ | <Link href={`/blog/${encodeURIComponent(post.path)}`}> | ||
+ | {post.name} | ||
+ | </Link> | ||
+ | </li> | ||
+ | ))} | ||
+ | </ul> | ||
+ | ) | ||
+ | } | ||
- | <code html> | + | export default Home; |
- | <form> | + | |
- | <label for="feedback">Lab Feedback</label> | + | |
- | <input | + | |
- | type="text" | + | |
- | id="feedback" | + | |
- | name="feedback" | + | |
- | required | + | |
- | minLength="20" | + | |
- | maxLength="40" | + | |
- | /> | + | |
- | <label for="name">Name:</label> | + | |
- | <input type="text" id="name" name="name" required /> | + | |
- | <button type="submit">Submit</button> | + | |
- | </form> | + | |
</code> | </code> | ||
- | With these validation checks in place, when a user tries to submit an empty field for Name, it gives an error that pops right in the form field. Similarly, the feedback must be between 20 and 40 characters long. | + | As an alternative, if you want to navigate between pages dynamically in a client component, from your JavaScript logic, you can do that with **//useRouter//**. |
- | + | ||
- | {{:se:labs:error.png?500|}} | + | |
- | + | ||
- | ===== JavaScript-based Form Validation ===== | + | |
- | + | ||
- | JavaScript offers an additional level of validation along with HTML native form attributes on the client side. | + | |
+ | |||
<code javascript> | <code javascript> | ||
- | function validateFormWithJS() { | + | 'use client' |
- | const name = nameRef.current.value; | + | import { useRouter } from 'next/navigation'; |
- | const feedback = feedbackRef.current.value; | + | |
- | if (!name) { | + | function Blog({ completePostpath }) { |
- | alert('Please enter your name.') | + | const router = useRouter(); |
- | return false | + | const handleClick = () => router.push(completePostpath); // e.g. /blog/first-post |
- | } | + | |
- | if (feedback.length < 20) { | + | return ( |
- | alert('Feedback should be at least 20 characters long.') | + | <div> |
- | return false | + | <p>Welcome!</p> |
- | } | + | <button onClick={handleClick}>My first blog post</button> |
- | } | + | </div> |
- | + | ) | |
- | return( | + | } |
- | <form onSubmit={validateFormWithJS}> | + | |
- | <label for="feedback">Lab Feedback</label> | + | export default Blog; |
- | <input | + | |
- | type="text" | + | |
- | id="feedback" | + | |
- | name="feedback" | + | |
- | ref={feedbackRef} | + | |
- | /> | + | |
- | <label for="name">Name:</label> | + | |
- | <input ref={nameRef} type="text" id="name" name="name" required /> | + | |
- | <button type="submit">Submit</button> | + | |
- | </form> | + | |
- | ) | + | |
- | + | ||
</code> | </code> | ||
+ | <note tip> | ||
+ | Read more about linking and navigation [[https://nextjs.org/docs/app/building-your-application/routing/linking-and-navigating|here]]. | ||
+ | </note> | ||
- | ===== React useRef() hook ===== | + | ===== Layouts ===== |
- | In most cases, we recommend using controlled components to implement forms. In a controlled component, form data is handled by a React component. The alternative is uncontrolled components, where form data is handled by the DOM itself. | + | If you want to reuse components on multiple pages, [[https://nextjs.org/docs/app/building-your-application/routing/layouts-and-templates#layouts|**layouts**]] come in handy. For instance, your app might have a header and a footer on each page: |
- | To write an uncontrolled component, instead of writing an event handler for every state update, you can use a ref to get form values from the DOM. | + | <code javascript> |
+ | // app/article/layout.tsx | ||
+ | // this layout will be the layout for all pages in the article folder and it's children | ||
- | useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue). The returned object will persist for the full lifetime of the component. | + | import Navbar from '..components/navbar'; |
+ | import Footer from '..components/footer'; | ||
- | You might be familiar with refs primarily as a way to access the DOM. If you pass a ref object to React with <div ref={myRef} />, React will set its .current property to the corresponding DOM node whenever that node changes. | + | export default function ArticleLayout({ children }) { |
- | + | ||
- | <code javascript> | + | |
- | function GetTextAreaDataUsingUseRef() { | + | |
- | const inputEl = React.useRef(); | + | |
- | const onButtonClick = () => { | + | |
- | // `current` points to the mounted text input element | + | |
- | console.log(inputEl.current.value); | + | |
- | }; | + | |
return ( | return ( | ||
- | <div> | + | <> |
- | <input ref={inputEl} type="text" /> | + | <Navbar /> |
- | <button onClick={onButtonClick}>Focus the input</button> | + | <main>{children}</main> |
- | </div> | + | <Footer /> |
+ | </> | ||
); | ); | ||
} | } | ||
</code> | </code> | ||
- | ===== Promises ===== | ||
- | The Promise object represents the eventual completion (or failure) of an asynchronous operation and its resulting value. | ||
- | Here, we create a promise that will resolve after 300ms. | + | Multiple layouts can also be used on a per-page basis. You can find more about that [[https://nextjs.org/docs/app/building-your-application/routing/layouts-and-templates#nesting-layouts|here]]. |
- | <code javascript> | + | |
- | const myPromise = new Promise((resolve, reject) => { | + | |
- | setTimeout(() => { | + | |
- | resolve("foo"); | + | |
- | }, 300); | + | |
- | }); | + | |
- | </code> | + | |
- | The Promise.resolve() method "resolves" a given value to a Promise. If the value is a promise, that promise is returned; if the value is a thenable, Promise.resolve() will call the then() method with two callbacks it prepared; otherwise the returned promise will be fulfilled with the value. | ||
- | A Promise that is resolved with the given value, or the promise passed as value, if the value was a promise object. It may be either fulfilled or rejected — for example, resolving a rejected promise will still result in a rejected promise. | + | ===== Data fetching ===== |
+ | In both server and client components [[https://nextjs.org/docs/app/building-your-application/data-fetching/fetching|data is fetched using]] the native fetch method. That being said it is recommended to avoid using it directly in combination with fetch inside client components but rather use a React library optimized for this pattern like [[https://swr.vercel.app|SWR]] which adds support for use SWR | ||
<code javascript> | <code javascript> | ||
- | const promise1 = Promise.resolve('lab4'); | + | // server side component fetch |
- | + | export default async function Page() { | |
- | promise1.then((value) => { | + | let data = await fetch('https://api.vercel.app/blog') |
- | console.log(value); | + | let posts = await data.json() |
- | // expected output: lab4 | + | return ( |
- | }); | + | <ul> |
+ | {posts.map((post) => ( | ||
+ | <li key={post.id}>{post.title}</li> | ||
+ | ))} | ||
+ | </ul> | ||
+ | ) | ||
+ | } | ||
</code> | </code> | ||
- | |||
- | === Async/Await === | ||
- | |||
- | An async function is a function declared with the async keyword, and the await keyword is permitted within it. The async and await keywords enable asynchronous, promise-based behavior to be written in a cleaner style, avoiding the need to explicitly configure promise chains. | ||
<code javascript> | <code javascript> | ||
- | async function foo() { | + | // client side component fetch with useEffect |
- | await 'lab4'; | + | 'use client' |
+ | |||
+ | import { useState, useEffect } from 'react' | ||
+ | |||
+ | export function Posts() { | ||
+ | const [posts, setPosts] = useState(null) | ||
+ | |||
+ | useEffect(() => { | ||
+ | async function fetchPosts() { | ||
+ | let res = await fetch('https://api.vercel.app/blog') | ||
+ | let data = await res.json() | ||
+ | setPosts(data) | ||
+ | } | ||
+ | fetchPosts() | ||
+ | }, []) | ||
+ | |||
+ | if (!posts) return <div>Loading...</div> | ||
+ | |||
+ | return ( | ||
+ | <ul> | ||
+ | {posts.map((post) => ( | ||
+ | <li key={post.id}>{post.title}</li> | ||
+ | ))} | ||
+ | </ul> | ||
+ | ) | ||
} | } | ||
</code> | </code> | ||
- | |||
- | Is similar to: | ||
<code javascript> | <code javascript> | ||
- | function foo() { | + | //swr |
- | return Promise.resolve('lab4').then(() => undefined); | + | import useSWR from 'swr' |
+ | |||
+ | function Profile() { | ||
+ | const { data, error, isLoading } = useSWR('/api/user', fetcher) | ||
+ | |||
+ | if (error) return <div>failed to load</div> | ||
+ | if (isLoading) return <div>loading...</div> | ||
+ | return <div>hello {data.name}!</div> | ||
} | } | ||
</code> | </code> | ||
- | The async and await keywords are tools to manage promises. | ||
- | If you mark a function as async then: | + | ===== Server Actions and Mutations ===== |
- | * its normal return value is replaced with a promise that resolves to its normal return value. | + | [[https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations|Server Actions]] are asynchronous functions that are executed on the server. They can be called in Server and Client Components to handle form submissions and data mutations in Next.js applications. |
- | * you may use await inside it | + | |
- | If you use await on the left hand side of a promise, then the containing function will go to sleep until the promise settles. Execution outside the async function will continue while it sleeps (i.e. is is not halted). | + | Server actions can be used in forms with the action attribute <form action={serverAction}></form>, which will call your serverAction(formData: FormData} function and pass all the forms inputs to it. |
- | The await keyword is only valid inside async functions within regular JavaScript code. If you use it outside of an async function's body, you will get a SyntaxError. | + | <code javascript> |
+ | // Inside server components, actions can be coded directly as | ||
+ | // a function as long as the 'user server' directive is written, | ||
+ | // telling next.js that the given function should run only on the server. | ||
+ | // This example does not use the data to process it in any way, | ||
+ | // it is just showing you how you can process the data | ||
+ | // It is recommended unless the code is trivial and small, | ||
+ | // to put all actions in a separate file implementing the logic of the components | ||
+ | export default function Page() { | ||
+ | async function createInvoice(formData: FormData) { | ||
+ | 'use server' | ||
+ | |||
+ | const rawFormData = { | ||
+ | customerId: formData.get('customerId'), | ||
+ | amount: formData.get('amount'), | ||
+ | status: formData.get('status'), | ||
+ | } | ||
+ | |||
+ | // mutate data | ||
+ | // revalidate cache | ||
+ | } | ||
+ | |||
+ | return <form action={createInvoice}>...</form> | ||
+ | } | ||
+ | </code> | ||
- | ===== Axios ===== | + | While actions can't be used directly inside a client component, they can be defined in a separate file and then used directly in the component. Behind the scenes, this will send a fetch request to the server, execute the function on the server, and return the result to the browser. |
- | + | ||
- | Axios is a promise-based HTTP Client for node.js and the browser. It can run in the browser and nodejs with the same codebase. On the server-side it uses the native node.js http module, while on the client (browser) it uses XMLHttpRequests. | + | |
- | + | ||
- | We are gonna use Axios to make HTTP requests. | + | |
- | + | ||
- | Example GET Request using AXIOS: | + | |
<code javascript> | <code javascript> | ||
- | axios.get('https://dummyjson.com/users') | + | // app/actions.ts |
- | .then(function (response) { | + | 'use server' |
- | // handle success | + | |
- | console.log(response); | + | export async function create() { console.log('button clicked') } |
- | }); | + | //This code will run on the server and show in the server console |
- | + | //Nothing will be written in the browser's console | |
</code> | </code> | ||
- | |||
- | Example POST Request using AXIOS: | ||
<code javascript> | <code javascript> | ||
- | axios.post('https://dummyjson.com/users/add', { | + | // app/coomponents/some-button.tsx |
- | firstName: 'Prenume', | + | 'use client' |
- | lastName: 'Dumitrescu', | + | |
- | age: 30, | + | import { create } from '@/app/actions' |
- | /* other user data */ | + | |
- | }) | + | export function Button() { |
- | .then(function (response) { | + | return <button onClick={() => create()}>Create</button> |
- | console.log(response); | + | } |
- | }); | + | |
</code> | </code> | ||
- | ====== Tasks ====== | ||
- | - Download the generated project {{:se:labs:se-lab4-tasks.zip|}} (and run npm install and npm run dev) | + | ===== Next.js Configuration ===== |
- | - Create a product form: | + | |
- | - Implement the form in component/Form.js, the form should have the following: | + | There is a plethora of configurations supported by Next.js that can be written in the **next.config.js** file which resides at the root of your project. One can set environment variables, base paths for routes, internationalization, CDN support, path reroutes, etc. You can find more about that [[https://nextjs.org/docs/api-reference/next.config.js/introduction|here]]. |
- | - Title (required) | + | |
- | - Description (textarea, optional, length must be at least 30) | + | |
- | - Price (number, required) | + | ===== Tasks ===== |
- | - Discount percentage (optional, must be greater than 5%) | + | |
- | - A category selection drop down list (Smartphones/ Laptops etc) | + | <note tip> |
- | - For the next part we are going to use a public dummy API to simulate HTTP Requests. We are going to use the [[https://dummyjson.com/docs/products|dummy products api]] | + | For this project, we will use the default empty nextjs app template together with the components you created in the previous Lab. Since they are written with front-end interactivity in mind, we will upgrade them to client components. We will be storing the todolist data in memory on the server and mutate it with server actions/mutations. |
- | - Get a list of all products available and display them in any way you want (using a list, a table etc) | + | You will find the components in the app/components folder |
- | - When submitting the form we created above, add a new product. Use useRef() to access the values | + | The actions will be in the app/actions.ts file together with TODOs |
+ | </note> | ||
+ | |||
+ | - Download the {{:se:labs:lab-4.zip|project archive}} for this lab and run **//npm install//** and **//npm run dev//**. | ||
+ | - Take a look over the app/components/ToDoList.tsx file and compare it with your previous implementation for the React only app | ||
+ | - Take a look over the app/page.tsx wich implements the homepage of your website and notice how the ToDoList component is pre initialized with data, allowing it to be rendered on the server on the first page load. | ||
+ | - Implement the required mutations in the app/actions.tsx file in order to fully reimplement the functionality of the react app. All missing parts are marked with TODO comments | ||
+ | |||
+ | <note> | ||
+ | This app requires JS enabled in order to work. As an optional task you can try and make it work without JS at all. This will require changing all interactions into navigations and form submissions. For example the edit button for an item should take the user to the /edit/item-ids page wich displays the full list of todo items where the given items in the URL are editable | ||
+ | </note> | ||
====== Feedback ====== | ====== Feedback ====== | ||
- | Please take a minute to fill in the **[[https://forms.gle/NuXCJktudGzf4rLg6 | feedback form]]** for this lab. | + | Please take a minute to fill in the **[[https://forms.gle/PNZYNNZFrVChLjag8 | feedback form]]** for this lab. |