diff --git a/react-nextjs-workshop/img/angelo.jpg b/react-nextjs-workshop/img/angelo.jpg deleted file mode 100644 index cc1cb22..0000000 Binary files a/react-nextjs-workshop/img/angelo.jpg and /dev/null differ diff --git a/react-nextjs-workshop/img/n1.png b/react-nextjs-workshop/img/n1.png new file mode 100644 index 0000000..538bcba Binary files /dev/null and b/react-nextjs-workshop/img/n1.png differ diff --git a/react-nextjs-workshop/img/n2.png b/react-nextjs-workshop/img/n2.png new file mode 100644 index 0000000..758d21a Binary files /dev/null and b/react-nextjs-workshop/img/n2.png differ diff --git a/react-nextjs-workshop/img/n3.png b/react-nextjs-workshop/img/n3.png new file mode 100644 index 0000000..c6d722b Binary files /dev/null and b/react-nextjs-workshop/img/n3.png differ diff --git a/react-nextjs-workshop/img/n4.png b/react-nextjs-workshop/img/n4.png new file mode 100644 index 0000000..d37e2f3 Binary files /dev/null and b/react-nextjs-workshop/img/n4.png differ diff --git a/react-nextjs-workshop/img/n5.png b/react-nextjs-workshop/img/n5.png new file mode 100644 index 0000000..d865ff6 Binary files /dev/null and b/react-nextjs-workshop/img/n5.png differ diff --git a/react-nextjs-workshop/img/n6.png b/react-nextjs-workshop/img/n6.png new file mode 100644 index 0000000..0bd46bb Binary files /dev/null and b/react-nextjs-workshop/img/n6.png differ diff --git a/react-nextjs-workshop/img/n7.png b/react-nextjs-workshop/img/n7.png new file mode 100644 index 0000000..2ee24f4 Binary files /dev/null and b/react-nextjs-workshop/img/n7.png differ diff --git a/react-nextjs-workshop/presentation.md b/react-nextjs-workshop/presentation.md index 454b418..de8f9ae 100644 --- a/react-nextjs-workshop/presentation.md +++ b/react-nextjs-workshop/presentation.md @@ -1,28 +1,31 @@ -

Edit in Eraser

- -class: center, middle, inverse, small-images - # React + ## After you learn it, there is no way back -![](./img/ni_logo.png "") + +![](./img/ni_logo.png) --- # O que é? + - Antigamente utilizava-se javascript normal, mas com o decorrer do tempo começou-se a querer fazer sites mais complicados e começaram a surgir bibliotecas e outras ferramentas para isso ser mais fácil. - **React** é uma biblioteca com esse objetivo, de permitir que seja mais fácil fazer interfaces mais complicadas e reativas. + --- class: middle # Exemplo contador + - Como motivação do por quê de React, vamos mostrar um exemplo de um contador simples com um -indicador do valor do contador e um botão para o incrementar. + indicador do valor do contador e um botão para o incrementar. + --- class: middle # Javascript - Exemplo contador + ```js ``` + --- class: middle # React - Exemplo contador + ```js // Importamos por que o react uma biblioteca com ferramentas -import { useState } from "react"; +import { useState } from "react"; function Counter() => { const [counter, setCounter] = useState(0); @@ -56,40 +61,50 @@ function Counter() => { return

Valor do contador é: {counter}

} ``` + - Muito mais fácil de ler! + --- # Uma função a retornar uma espécie de HTML? + - **Sim**, são **componentes**, funções que retornam **JSX**, que é derivado de HTML -e feito para ser utilizado juntamente com javascript. + e feito para ser utilizado juntamente com javascript. - Para renderizar um componente, basta chamar a função da mesma maneira como se estivessemos -a colocar um elemento HTML. + a colocar um elemento HTML. + ## Por exemplo + .center[ -`App.jsx` +`App.jsx` ```js function App() { - return + return ; } ``` + ] --- # JSX - Diferenças com HTML -- `
` em HTML fica `
` em JSX. + +- `
` em HTML fica `
` em JSX. - Em JSX, os atributos são _camel case_. + ### Por exemplo -- `onclick` fica `onClick` -.flex-columns-center[ + +- `onclick` fica `onClick` + .flex-columns-center[ + ```html -
{ console.log("clicked"); @@ -98,80 +113,98 @@ function App() { Hidden div
``` + ] --- # Como passar argumentos aos componentes? -- Os componentes de react só recebem um argumento chamado `props` , que é um objeto. -.flex-columns-center[ + +- Os componentes de react só recebem um argumento chamado `props` , que é um objeto. + .flex-columns-center[ + ```js function PersonDisplay(props) { - return
-

{props.name}

-

{props.age} anos

-
+ return ( +
+

{props.name}

+

{props.age} anos

+
+ ); } ``` + ```js -function PersonDisplay({ - name, - age -}) { - return
-

{name}

-

{age} anos

-
+function PersonDisplay({ name, age }) { + return ( +
+

{name}

+

{age} anos

+
+ ); } ``` + ] --- # Mais um pormenor sobre props -- Por default, existe um argumento chamado `children` , que é automaticamente preenchido pelo React. -.flex-columns[ + +- Por default, existe um argumento chamado `children` , que é automaticamente preenchido pelo React. + .flex-columns[ + ```js function Box({ children }) { - return
- {children} -
+ return ( +
+ {children} +
+ ); } ``` + ```js -function PersonDisplay({ - name, - age -}) { - return
- -

{name}

-

{age} anos

-
-
+function PersonDisplay({ name, age }) { + return ( +
+ +

{name}

+

{age} anos

+
+
+ ); } ``` + ] --- # Quando é que os componentes são renderizados? + - Quando a página é carregada. -- Quando uma variável de estado (com `useState` ) é atualizada. +- Quando uma variável de estado (com `useState` ) é atualizada. - Quando um pai é re-renderizado. + ```js function App() { const [someState, setSomeState] = useState(0); - - {/*Se re-renderizar, o também será re-renderizado*/} - return + + { + /*Se re-renderizar, o também será re-renderizado*/ + } + return ; } ``` + --- # Renderização de componentes - Nota adicional -- Isto **não** funciona! O componente não vai ser re-renderizado e por isso o texto do `

` não vai ser alterado. O `useState` contém código que força a re-renderização do componente. -.flex-columns-center[ + +- Isto **não** funciona! O componente não vai ser re-renderizado e por isso o texto do `

` não vai ser alterado. O `useState` contém código que força a re-renderização do componente. + .flex-columns-center[ + ```js function Counter() => { const counter = 0; @@ -179,75 +212,87 @@ function Counter() => { return

Valor do contador é: {counter}

} ``` + ] --- # Nota sobre funcionamento de browsers + .flex-columns-center[ -![](./img/browser-workings.png "") +![](./img/browser-workings.png) - ] +] --- # DOM + - Os browsers constroem uma àrvore chamada **DOM** (Document Object Model) que representa as relações hierárquicas -dos elementos. -.flex-columns[ + dos elementos. + .flex-columns[ + ```html - - - Web page - - -

Web page

-

This is a simple web page

- + + + Web page + + +

Web page

+

This is a simple web page

+ ``` + ] --- # Virtual DOM + - Por motivos de eficiência, o React não altera diretamente na DOM as mudanças nos elementos para poder agrupar mudanças, em vez de fazer uma a uma. -- Alterar algo na `virtualDOM` não implica logo re-renderização por parte do browser, só quando é passado para a DOM real. +- Alterar algo na `virtualDOM` não implica logo re-renderização por parte do browser, só quando é passado para a DOM real. + --- # Keys -- Em vez de re-renderizar a lista toda quando ela muda, com as `keys` , o react permite identificar cada elemento individualmente. -.flex-columns-center[ + +- Em vez de re-renderizar a lista toda quando ela muda, com as `keys` , o react permite identificar cada elemento individualmente. + .flex-columns-center[ + ```javascript function App() { const requests = fetchRequests(); - + return <> {requests.map((request, index) => ( - )} } ``` + ] --- # Hooks - O que são? + - **Hooks** são **funções especiais** que são alocadas na memória RAM num local onde quando -retornam um valor continuam em memória com todas as variáveis dentro dela intactas. **Começam sempre com um **`**use[A-Z]**` . -.flex-columns[ + retornam um valor continuam em memória com todas as variáveis dentro dela intactas. **Começam sempre com um **`**use[A-Z]**` . + .flex-columns[ + ```js function counter() { const counter = 10; @@ -255,6 +300,7 @@ function counter() { counter(); // A partir daqui a função deixa de estar em memória ``` + ```js function useCounter() { const counter = 10; @@ -262,97 +308,123 @@ function useCounter() { useCounter(); // A partir daqui a função continua em memória ``` + ] --- # Mas para o que é que isto seria útil? -- Lembram-se do `useState()` ? + +- Lembram-se do `useState()` ? + ```js -import { useState } from 'react'; +import { useState } from "react"; function Counter() { const [counter, setCounter] = useState(0); } ``` + - Como fica sempre em memória mesmo depois de ser chamado consegue guardar o estado do componente. -- E **é útil ser em formato de função** por que já imaginaram termos de colocar o código todo que está dentro do `useState` em cada componente que quiséssemos ter estado? +- E **é útil ser em formato de função** por que já imaginaram termos de colocar o código todo que está dentro do `useState` em cada componente que quiséssemos ter estado? + --- # Mais sobre hooks + - É mais fácil quando ouvirem _hooks_ traduzirem simplesmente para _funções especiais que guardam estado_. - Muitas vezes as pessoas não percebem esta parte dos _hooks_ por ser demasiado abstrato e não tratarem as coisas de forma mais específica. + ### Agora vamos falar sobre _hooks_ que a o React disponibiliza já feitos para nós usarmos + --- # useContext - Motivação (1/2) + - Nos exemplos anteriores, a àrvore era simples por que não tinhamos muitos componentes. Mas existem -circunstâncias onde muitos componentes precisam de aceder a uma mesma variável. -- Se por exemplo se tivessemos um componente `Account` que dentro dele tinha um `Profile` e dentro dele tinha um `UserBasicInfo` -a renderizar informações sobre o utilizador (nome, email, etc). -- Para `Account` , `Profile` e `UserBasicInfo` terem acesso ao utilizador, neste caso, teríamos de passar o estado da seguinte forma: `Account` -> `Profile` -> `UserBasicInfo` . + circunstâncias onde muitos componentes precisam de aceder a uma mesma variável. +- Se por exemplo se tivessemos um componente `Account` que dentro dele tinha um `Profile` e dentro dele tinha um `UserBasicInfo` + a renderizar informações sobre o utilizador (nome, email, etc). +- Para `Account` , `Profile` e `UserBasicInfo` terem acesso ao utilizador, neste caso, teríamos de passar o estado da seguinte forma: `Account` -> `Profile` -> `UserBasicInfo` . + --- # useContext - Motivação (2/2) + .flex-columns[ ```js function Account() { const { user } = fetchUser(); - return + return ; } function Profile({ user }) { - return + return ; } function UserBasicInfo({ user }) { - return
-

{user.name}

-

{user.email}

-
+ return ( +
+

{user.name}

+

{user.email}

+
+ ); } ``` + ] --- # useContext - Como receber valores? + ```js function Profile() { - return + return ; } ``` + ```js function UserBasicInfo() { - const {user} = useContext(UserContext); - return
-

{user.name}

-

{user.email}

-
+ const { user } = useContext(UserContext); + return ( +
+

{user.name}

+

{user.email}

+
+ ); } ``` + --- # useContext - Como criar valores? + ```js const UserContext = createContext({ user: undefined }}); ``` + ```js function App() { const user = fetchUser(); - return - - + return ( + + + + ); } ``` + --- # useEffect + - Uma função já fornecida pelo React que recebe uma função e um array de dependências, que permite correr código quando certas variáveis mudam ou quando o componente é renderizado. + ```js function Counter() => { const [counter, setCounter] = useState(localStorage.getItem("counter") || 0); @@ -364,25 +436,30 @@ function Counter() => { return

Valor do contador é: {counter}

} ``` + --- # Como tornar o código anterior mais modular? -- Se fossemos utilizar um contador com o objetivo de guardar na `localStorage` noutros componentes teríamos de estar -sempre a copiar o seguinte código. + +- Se fossemos utilizar um contador com o objetivo de guardar na `localStorage` noutros componentes teríamos de estar + sempre a copiar o seguinte código. + ```js const [counter, setCounter] = useState(localStorage.getItem("counter") || 0); useEffect(() => { localStorage.setItem("counter", counter); }, [counter]); ``` + --- # Criar um hook customizado + ```js function Counter() { const [counter, setCounter] = useLocalStorage("counter", 0); @@ -390,6 +467,7 @@ function Counter() { return ... } ``` + ```js function useLocalStorage(storageItem, defaultValue) { const [value, setValue] = useState(localStorage.getItem("counter") ?? 0); @@ -400,18 +478,22 @@ useEffect(() => { return [value, setValue]; ``` + --- # NextJS + Se não sabes React, nem devias estar aqui... Este é um workshop de Next.js **versão 13** para cima! --- +# O que é o Next.js? + Nextjs é uma **framework**. -É uma tecnologia que fornece uma estrutura para o desenvolvimento de websites com bastantes benificios como **SSR** - Server Side Rendering. +É uma tecnologia que fornece uma estrutura para o desenvolvimento de websites com bastantes benificios como **SSR** - Server Side Rendering. Mas não só, até é possível criar uma simples Rest API sem necessidade de envolver _frontend_. @@ -419,145 +501,150 @@ Next.js é construido em cima de React e utiliza as suas funções para mexer co --- -(n1) + --- ## SSR - Server Side Rendering / Dynamic Rendering + O conteudo é renderizado no servidor no momento em que o utilizador faz o _request_, isto é, visita a página. O cliente recebe diretamente o html resultante. ### Vantagens + - É muito fácil fornecer **informação personalizada** com base no utilizador, como por exemplo dashboards e perfil. - Acesso a informações do _request_ como _Cookies_ e _URL Search Params_ no momento. - Websites mais rápidos pois as páginas podem ser guardadas em _**cache**_ e globalmente distribuidas. - O **Server Load é reduzido** dado que o conteudo é guardado em cache e não necessita de ser gerado novamente caso as informações não sejam modificadas. - **SEO** - Os crawlers indexam muito melhor os websites que carregam a informação no momento em que a página acontece. Isto leva a melhores rankings de pesquisa. + --- # Estrutura de Ficheiros + ## Routing - App Router + Esta é uma das grandes mudanças na versão 13. -> Tenham sempre cuidado a ver a documentação e se estão a ver a versão correta +> Tenham sempre cuidado a ver a documentação e se estão a ver a versão correta -(n2) + -`.ts = .js + TypeScript ` +`.ts = .js + TypeScript ` -`.tsx = .jsx + TypeScript` +`.tsx = .jsx + TypeScript` --- -## Routing - App Router + + - page.tsx e _nesting_ de folders - layout.tsx -(n4) + + > Um dos benefícios de usar layout é que ao mudar de página, os componentes do layout não voltam a ser renderizados. - loading.jsx - not-found.jsx - error.jsx + --- -## Routing - App Router ### Dynamic Routing -(n5) -``` + + +```tsx export default function Page({ params }: { params: { slug: string } }) { - return
My Post: {params.slug}
+ return
My Post: {params.slug}
; } ``` + --- -## Routing - App Router ### APIs -(n6) -`request` e `context` são opcionais + -``` +```tsx +//`request` e `context` são opcionais export async function GET(request: Request) {} - export async function HEAD(request: Request, context: { params: Params }) {} - export async function POST(request: Request) { - return NextResponse.json({ error: 'ISE' }, { status: 500 }) + return NextResponse.json({ error: "ISE" }, { status: 500 }); } - export async function PUT(request: Request) {} - export async function DELETE(request: Request) {} - export async function PATCH(request: Request) {} - -// If `OPTIONS` is not defined, Next.js will automatically implement `OPTIONS` and set the appropriate Response `Allow` header depending on the other methods defined in the route handler. export async function OPTIONS(request: Request) {} ``` -> Não pode existir `layout.tsx` nem `page.tsx` ao mesmo nível do route.ts. Tipicamente cria-se uma pasta `api` para servir a API. + +> Não pode existir `layout.tsx` nem `page.tsx` ao mesmo nível do route.ts. Tipicamente cria-se uma pasta `api` para servir a API. --- -## Routing - App Router ### Other Project Organization Features + - Pastas privadas, a começar com `_`. Estas pastas não serão consideradas no _routing_ da app. - Route groups, nome da pasta entre `()`. Desta forma é possível organizar pastas sem interferir no _routing_ da app. + --- ## Metadata -`page.tsx` -``` +```tsx +// Static export const metadata: Metadata = { - title: 'Título super interessante', - description: 'Os oradores são mesmo nice' + title: "Título super interessante", + description: "Os oradores são mesmo nice", }; ``` -Dynamic Metadata - -``` -export async function generateMetadata( - { params }: { params: { id: string } } -): Promise { - - const id = params.id // read route params - - const product = await fetch(`https://.../${id}`).then((res) => res.json()) +```tsx +// Dynamic +export async function generateMetadata({ + params, +}: { + params: { id: string }; +}): Promise { + const id = params.id; + const product = await fetch(`https://.../${id}`).then((res) => res.json()); return { title: product.title, openGraph: { - images: ['/some-specific-page-image.jpg'], + images: ["/some-specific-page-image.jpg"], }, - } + }; } ``` - --- -## -``` -import Link from 'next/link' -``` +## Link + +```tsx +import Link from "next/link"; ``` + +```tsx Dashboard ``` + > Nunca usem tags `` em Next.js! --- -## -``` -import Image from 'next/image' -``` -``` +## Image + +```tsx +import Image from "next/image"; + Picture of the author +/>; ``` + Uma das razões dos _websites_ de tornarem lentos ao carregar, é exatamente o carregamento das imagens. Vantagens: @@ -566,9 +653,11 @@ Vantagens: - Redimensionamento automático para evitar enviar imagens grandes para dispositivos pequenos. - _Lazy loading_ por definição. As imagens vão sendo carregadas à medida que vão a aparecer na janela de visualização. - Formatos modernos, como [WebP](https://developer.mozilla.org/pt-BR/docs/Web/Media/Formats/Image_types#webp) e [AVIF](https://developer.mozilla.org/pt-BR/docs/Web/Media/Formats/Image_types#avif_image), quando o _browser_ é compatível. + --- -## "use server" vs "use client" +# "use server" vs "use client" + Por definição, todas as páginas são "use server", exceto escrito o contrário. O **"use client"** significa que página será renderizada do lado do cliente. Isto significa que um bundle de JavaScript será enviado para o cliente e serão possíveis usar os _hooks_ do React, tal como _useState_ e _useEffect._ @@ -579,44 +668,50 @@ A partir do momento que usamos **"use client"** num componente, não podemos vol --- -****## "use server" vs "use client" -(n7) +# "use server" vs "use client" + + --- ## Server Actions + - Funções assíncronas - Rodam no servidor - Podem ser chamadas no lado do cliente. -``` -'use server' - + +```tsx +"use server"; + export async function updateItem(itemId, formData) { // database stuff here } ``` -``` -'use client' -import { updateItem } from './actions' - -export default function ClientComponent({itemId}) { - return
{/* ... */}
+```tsx +"use client"; + +import { updateItem } from "./actions"; + +export default function ClientComponent({ itemId }) { + return
{/* ... */}
; } ``` + Como enviar o `itemId` para o `updateItem`? --- -## Server Actions - [Pending states](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#pending-states) - +## Server Actions - [Pending states](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#pending-states) --- -## Filosofia da framework! +# Filosofia da framework! + "We recommend first attempting to **fetch data on the server-side**." -- "However, there are still cases where client-side data fetching makes sense. In these scenarios, you can manually call `fetch` in a `useEffect` (not recommended), or lean on popular React libraries in the community such as [SWR](https://swr.vercel.app/) or [React Query](https://tanstack.com/query/latest) ." +- "However, there are still cases where client-side data fetching makes sense. In these scenarios, you can manually call `fetch` in a `useEffect` (not recommended), or lean on popular React libraries in the community such as [SWR](https://swr.vercel.app/) or [React Query](https://tanstack.com/query/latest) ." + "To reduce the Client JavaScript bundle size, we recommend moving **Client Components down your component tree**." "Optimizations, whenever is possible" @@ -624,35 +719,40 @@ Como enviar o `itemId` para o `updateItem`? --- ## Coisas interessantes para explorar em casa + - Middleware - ORM (Prisma or Drizzle) -- Parallel Routes & Tab Groups +- Parallel Routes & Tab Groups - Interception Routes - Internationalization -- React [useOptimistic](https://react.dev/reference/react/useOptimistic) +- React [useOptimistic](https://react.dev/reference/react/useOptimistic) - NextAuth + --- # Bora ao trabalho! -> Mini site para inserir links de recursos interessantes da L.EIC. - +> Mini site para inserir links de recursos interessantes da L.EIC. Requisitos: - [Node.js](https://nodejs.org/en) acima da versão 18 -Primeiros passos: + Primeiros passos: + +- `git clone <>` +- `cp .env.example .env` + - Mudar o `` + - Mudar a `` +- `npm install` +- `npx prisma generate` -- `git clone <>` -- `cp .env.example .env` - - Mudar o `` - - Mudar a `` -- `npm install` -- `npx prisma generate` --- -## Tarefa #1  - Primeiro Componente em React -`app/components/feed-resource-card.tsx` +# Tarefa #1 + +## Primeiro Componente em React + +`app/components/feed-resource-card.tsx` Implementa o componente `ResourceCard` . @@ -660,22 +760,30 @@ Tens um html para te ajudar! --- -## Tarefa #2 - Listar os recursos no feed -`app/@feed/page.tsx` +# Tarefa #2 + +## Listar os recursos no feed + +`app/@feed/page.tsx` - Se não existirem recursos, mostrar uma mensagem a dizer "No resources found", exemplo: + ```
No resources found.
``` + - Se existirem recursos, mostrar uma lista de ResourceCard. -> Não te esqueças do `key` + > Não te esqueças do `key` --- -## Tarefa #3 - Adptar a página de um recurso -`app/resources/[id]/page.tsx` +# Tarefa #3 + +## Adptar a página de um recurso + +`app/resources/[id]/page.tsx` Consoante o recurso selecionado através do url, adapta a página para mostrar informações relevantes desse recurso. @@ -684,62 +792,67 @@ Consoante o recurso selecionado através do url, adapta a página para mostrar i - Se o recurso não existir, podes mostrar uma página de erro 404. - Se o recurso não tiver `thumbnail`, usa a imagem que existe em `/public/images/placeholder.jpg`. - Bónus: - Cria uma função para copiar o url da página ao clicar no botão share. - --- -## Tarefa #4 - Escreve o Metadata da página do recurso -`app/resources/[id]/page.tsx` +# Tarefa #4 + +## Escreve o Metadata da página do recurso + +`app/resources/[id]/page.tsx` Inspeciona o _website_ e verifica dentro do `` se o título e a descrição estão a ser preenchidos. > Dica: Consulta os slides à procura do generateMetadata. +--- +# Tarefa #5 ---- +## Criar uma página 404 para um recurso não encontrado -## Tarefa #5 - Criar uma página 404 para um recurso não encontrado Qual é o ficheiro que deves criar e onde deves colocar? --- -## Tarefa #6 - Escreve o Metadata da página do recurso -`app/resources/[id]/counter.tsx` +# Tarefa #6 + +## Escreve o Metadata da página do recurso -(n8) +`app/resources/[id]/counter.tsx` - O contador deve ser incrementado sempre que o botão for clicado - Mostra o número de reações - (Bonus) Guarda o número de reações no localStorage e acessa o valor guardado ao carregar a página + --- -## Tarefa #7 - Completa a Server Action para adicionar um recurso novo -`lib/actions/resources.ts` +# Tarefa #7 -Acaba de implementar a Server Action que adiciona um recurso novo! +## Completa a Server Action para adicionar um recurso novo + +`lib/actions/resources.ts` A função recebe um campo "url" a partir de um FormData. - Deves ver se o recurso já existe e se sim, retornar um erro -- Obter informação do recurso submetido a partir do URL. -> Dica: Ver as funções importadas +- Obter informação do recurso submetido a partir do URL. -- Criar o recurso na base de dados. -> Dica: Ver class `Database` + > Dica: Ver as funções importadas -- Revalidar o cache da página inicial ---- +- Criar o recurso na base de dados. -# É tudo por hoje! -Obrigado + > Dica: Ver class `Database` +- Revalidar o cache da página inicial +--- +# É tudo por hoje! +## Obrigado - \ No newline at end of file +---