mirror of
https://github.com/wasp-lang/wasp.git
synced 2024-11-23 01:54:37 +03:00
Update Todo app tutorial (#840)
* Update todo app tutorial * Remove trailing spaces from clocks * Match MainPage with tutorial * Add newline to end of action.js * Fix newline at the end of action.js * Fix typo in tutorial * Update web/docs/tutorials/todo-app/creating-new-project.md Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Update web/docs/tutorials/todo-app/creating-new-project.md Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com> * Fix jsx typo in tutorial * Remove React import from e2e tests Co-authored-by: Martin Šošić <Martinsos@users.noreply.github.com>
This commit is contained in:
parent
6be17151c9
commit
1f7f9005b0
@ -21,7 +21,7 @@ app TodoApp {
|
||||
route RootRoute { path: "/", to: MainPage }
|
||||
page MainPage {
|
||||
authRequired: true,
|
||||
component: import Main from "@client/MainPage.js"
|
||||
component: import Main from "@client/MainPage"
|
||||
}
|
||||
|
||||
route SignupRoute { path: "/signup", to: SignupPage }
|
||||
@ -50,16 +50,16 @@ entity Task {=psl
|
||||
psl=}
|
||||
|
||||
query getTasks {
|
||||
fn: import { getTasks } from "@server/queries.js",
|
||||
fn: import { getTasks } from "@server/queries",
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
action createTask {
|
||||
fn: import { createTask } from "@server/actions.js",
|
||||
fn: import { createTask } from "@server/actions",
|
||||
entities: [Task]
|
||||
}
|
||||
|
||||
action updateTask {
|
||||
fn: import { updateTask } from "@server/actions.js",
|
||||
fn: import { updateTask } from "@server/actions",
|
||||
entities: [Task]
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import Clock from 'react-clock'
|
||||
import 'react-clock/dist/Clock.css'
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import LoginForm from '@wasp/auth/forms/Login'
|
@ -1,10 +1,8 @@
|
||||
import React from 'react'
|
||||
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
import { useQuery } from '@wasp/queries'
|
||||
import getTasks from '@wasp/queries/getTasks'
|
||||
import createTask from '@wasp/actions/createTask'
|
||||
import updateTask from '@wasp/actions/updateTask'
|
||||
import logout from '@wasp/auth/logout'
|
||||
import Clocks from './Clocks'
|
||||
|
||||
const MainPage = () => {
|
||||
@ -37,7 +35,6 @@ const Task = (props) => {
|
||||
window.alert('Error while updating task: ' + error.message)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<input
|
||||
@ -55,7 +52,7 @@ const TasksList = (props) => {
|
||||
return props.tasks.map((task, idx) => <Task task={task} key={idx} />)
|
||||
}
|
||||
|
||||
const NewTaskForm = () => {
|
||||
const NewTaskForm = (props) => {
|
||||
const handleSubmit = async (event) => {
|
||||
event.preventDefault()
|
||||
try {
|
||||
@ -80,3 +77,4 @@ const NewTaskForm = () => {
|
||||
}
|
||||
|
||||
export default MainPage
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import SignupForm from '@wasp/auth/forms/Signup'
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// The following settings enable IDE support in user-provided source files.
|
||||
// Editing them might break features like import autocompletion and
|
||||
// definition lookup. Don't change them unless you know what you're doing.
|
||||
//
|
||||
// The relative path to the generated web app's root directory. This must be
|
||||
// set to define the "paths" option.
|
||||
"baseUrl": "../../.wasp/out/web-app/",
|
||||
"paths": {
|
||||
// Resolve all "@wasp" imports to the generated source code.
|
||||
"@wasp/*": [
|
||||
"src/*"
|
||||
],
|
||||
// Resolve all non-relative imports to the correct node module. Source:
|
||||
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
|
||||
"*": [
|
||||
// Start by looking for the definiton inside the node modules root
|
||||
// directory...
|
||||
"node_modules/*",
|
||||
// ... If that fails, try to find it inside definitely-typed type
|
||||
// definitions.
|
||||
"node_modules/@types/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
60
examples/tutorials/TodoApp/src/client/react-app-env.d.ts
vendored
Normal file
60
examples/tutorials/TodoApp/src/client/react-app-env.d.ts
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
declare module '*.avif' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.bmp' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.gif' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.jpeg' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.png' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.webp' {
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.svg' {
|
||||
import * as React from 'react';
|
||||
|
||||
export const ReactComponent: React.FunctionComponent<React.SVGProps<
|
||||
SVGSVGElement
|
||||
> & { title?: string }>;
|
||||
|
||||
const src: string;
|
||||
export default src;
|
||||
}
|
||||
|
||||
declare module '*.module.css' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.scss' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
||||
|
||||
declare module '*.module.sass' {
|
||||
const classes: { readonly [key: string]: string };
|
||||
export default classes;
|
||||
}
|
55
examples/tutorials/TodoApp/src/client/tsconfig.json
Normal file
55
examples/tutorials/TodoApp/src/client/tsconfig.json
Normal file
@ -0,0 +1,55 @@
|
||||
// =============================== IMPORTANT =================================
|
||||
//
|
||||
// This file is only used for Wasp IDE support. You can change it to configure
|
||||
// your IDE checks, but none of these options will affect the TypeScript
|
||||
// compiler. Proper TS compiler configuration in Wasp is coming soon :)
|
||||
{
|
||||
"compilerOptions": {
|
||||
// JSX support
|
||||
"jsx": "preserve",
|
||||
"strict": true,
|
||||
// Allow default imports.
|
||||
"esModuleInterop": true,
|
||||
"lib": [
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"esnext"
|
||||
],
|
||||
"allowJs": true,
|
||||
// Wasp needs the following settings enable IDE support in your source
|
||||
// files. Editing them might break features like import autocompletion and
|
||||
// definition lookup. Don't change them unless you know what you're doing.
|
||||
//
|
||||
// The relative path to the generated web app's root directory. This must be
|
||||
// set to define the "paths" option.
|
||||
"baseUrl": "../../.wasp/out/web-app/",
|
||||
"paths": {
|
||||
// Resolve all "@wasp" imports to the generated source code.
|
||||
"@wasp/*": [
|
||||
"src/*"
|
||||
],
|
||||
// Resolve all non-relative imports to the correct node module. Source:
|
||||
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
|
||||
"*": [
|
||||
// Start by looking for the definiton inside the node modules root
|
||||
// directory...
|
||||
"node_modules/*",
|
||||
// ... If that fails, try to find it inside definitely-typed type
|
||||
// definitions.
|
||||
"node_modules/@types/*"
|
||||
]
|
||||
},
|
||||
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
|
||||
"typeRoots": [
|
||||
"../../.wasp/out/web-app/node_modules/@types"
|
||||
],
|
||||
// Since this TS config is used only for IDE support and not for
|
||||
// compilation, the following directory doesn't exist. We need to specify
|
||||
// it to prevent this error:
|
||||
// https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file
|
||||
"outDir": "phantom"
|
||||
},
|
||||
"exclude": [
|
||||
"phantom"
|
||||
],
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
@ -1,21 +1,19 @@
|
||||
import HttpError from '@wasp/core/HttpError.js'
|
||||
|
||||
export const createTask = async ({ description }, context) => {
|
||||
export const createTask = async (args, context) => {
|
||||
if (!context.user) { throw new HttpError(401) }
|
||||
return context.entities.Task.create({
|
||||
data: {
|
||||
description,
|
||||
description: args.description,
|
||||
user: { connect: { id: context.user.id } }
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const updateTask = async ({ taskId, data }, context) => {
|
||||
export const updateTask = async (args, context) => {
|
||||
if (!context.user) { throw new HttpError(401) }
|
||||
return context.entities.Task.updateMany({
|
||||
where: { id: taskId, user: { id: context.user.id } },
|
||||
data: {
|
||||
isDone: data.isDone
|
||||
}
|
||||
where: { id: args.taskId, user: { id: context.user.id } },
|
||||
data: { isDone: args.data.isDone }
|
||||
})
|
||||
}
|
||||
|
@ -1,27 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// The following settings enable IDE support in user-provided source files.
|
||||
// Editing them might break features like import autocompletion and
|
||||
// definition lookup. Don't change them unless you know what you're doing.
|
||||
//
|
||||
// The relative path to the generated web app's root directory. This must be
|
||||
// set to define the "paths" option.
|
||||
"baseUrl": "../../.wasp/out/server/",
|
||||
"paths": {
|
||||
// Resolve all "@wasp" imports to the generated source code.
|
||||
"@wasp/*": [
|
||||
"src/*"
|
||||
],
|
||||
// Resolve all non-relative imports to the correct node module. Source:
|
||||
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
|
||||
"*": [
|
||||
// Start by looking for the definiton inside the node modules root
|
||||
// directory...
|
||||
"node_modules/*",
|
||||
// ... If that fails, Try to find it inside definitely-typed type
|
||||
// definitions.
|
||||
"node_modules/@types/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
48
examples/tutorials/TodoApp/src/server/tsconfig.json
Normal file
48
examples/tutorials/TodoApp/src/server/tsconfig.json
Normal file
@ -0,0 +1,48 @@
|
||||
// =============================== IMPORTANT =================================
|
||||
//
|
||||
// This file is only used for Wasp IDE support. You can change it to configure
|
||||
// your IDE checks, but none of these options will affect the TypeScript
|
||||
// compiler. Proper TS compiler configuration in Wasp is coming soon :)
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Allows default imports.
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
"strict": true,
|
||||
// Wasp needs the following settings enable IDE support in your source
|
||||
// files. Editing them might break features like import autocompletion and
|
||||
// definition lookup. Don't change them unless you know what you're doing.
|
||||
//
|
||||
// The relative path to the generated web app's root directory. This must be
|
||||
// set to define the "paths" option.
|
||||
"baseUrl": "../../.wasp/out/server/",
|
||||
"paths": {
|
||||
// Resolve all "@wasp" imports to the generated source code.
|
||||
"@wasp/*": [
|
||||
"src/*"
|
||||
],
|
||||
// Resolve all non-relative imports to the correct node module. Source:
|
||||
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
|
||||
"*": [
|
||||
// Start by looking for the definiton inside the node modules root
|
||||
// directory...
|
||||
"node_modules/*",
|
||||
// ... If that fails, try to find it inside definitely-typed type
|
||||
// definitions.
|
||||
"node_modules/@types/*"
|
||||
]
|
||||
},
|
||||
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
|
||||
"typeRoots": [
|
||||
"../../.wasp/out/server/node_modules/@types"
|
||||
],
|
||||
// Since this TS config is used only for IDE support and not for
|
||||
// compilation, the following directory doesn't exist. We need to specify
|
||||
// it to prevent this error:
|
||||
// https://stackoverflow.com/questions/42609768/typescript-error-cannot-write-file-because-it-would-overwrite-input-file
|
||||
"outDir": "phantom",
|
||||
},
|
||||
"exclude": [
|
||||
"phantom"
|
||||
],
|
||||
}
|
@ -1,12 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Enable default imports in TypeScript.
|
||||
"esModuleInterop": true,
|
||||
"allowJs": true,
|
||||
// The following settings enable IDE support in user-provided source files.
|
||||
// Editing them might break features like import autocompletion and
|
||||
// definition lookup. Don't change them unless you know what you're doing.
|
||||
//
|
||||
// The relative path to the generated web app's root directory. This must be
|
||||
// set to define the "paths" option.
|
||||
"baseUrl": "../../.wasp/out/server",
|
||||
"baseUrl": "../../.wasp/out/server/",
|
||||
"paths": {
|
||||
// Resolve all non-relative imports to the correct node module. Source:
|
||||
// https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping
|
||||
@ -18,6 +21,8 @@
|
||||
// definitions.
|
||||
"node_modules/@types/*"
|
||||
]
|
||||
}
|
||||
},
|
||||
// Correctly resolve types: https://www.typescriptlang.org/tsconfig#typeRoots
|
||||
"typeRoots": ["../../.wasp/out/server/node_modules/@types"]
|
||||
}
|
||||
}
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -270,7 +270,7 @@
|
||||
"file",
|
||||
"web-app/src/ext-src/MainPage.jsx"
|
||||
],
|
||||
"612bc8f76682b8753ffe94c81fb58968d60867071d412b4e931918d75ad89bf8"
|
||||
"7244c106359f088fdcc0d4a76ee63277f1cea63cbe7aac5e5c39a17df693b1e2"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -270,7 +270,7 @@
|
||||
"file",
|
||||
"web-app/src/ext-src/MainPage.jsx"
|
||||
],
|
||||
"612bc8f76682b8753ffe94c81fb58968d60867071d412b4e931918d75ad89bf8"
|
||||
"7244c106359f088fdcc0d4a76ee63277f1cea63cbe7aac5e5c39a17df693b1e2"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -284,7 +284,7 @@
|
||||
"file",
|
||||
"web-app/src/ext-src/MainPage.jsx"
|
||||
],
|
||||
"612bc8f76682b8753ffe94c81fb58968d60867071d412b4e931918d75ad89bf8"
|
||||
"7244c106359f088fdcc0d4a76ee63277f1cea63cbe7aac5e5c39a17df693b1e2"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -270,7 +270,7 @@
|
||||
"file",
|
||||
"web-app/src/ext-src/MainPage.jsx"
|
||||
],
|
||||
"612bc8f76682b8753ffe94c81fb58968d60867071d412b4e931918d75ad89bf8"
|
||||
"7244c106359f088fdcc0d4a76ee63277f1cea63cbe7aac5e5c39a17df693b1e2"
|
||||
],
|
||||
[
|
||||
[
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import React from 'react'
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
|
@ -5,11 +5,11 @@ title: Overview
|
||||
Wasp is a declarative language that recognizes web application-specific terms (e.g. *page* or *route*) as
|
||||
words (types) of the language.
|
||||
|
||||
The basic idea is that the higher-level overview of an app (e.g. pages, routes, database model, ...) is defined in `*.wasp` files (for now just one), while the specific parts (web components, back-end queries, ...) are implemented in specific non-wasp technologies (React, NodeJS, Prisma) and then referenced in the `*.wasp` files.
|
||||
The basic idea is that the higher-level overview of an app (e.g. pages, routes, database model, ...) is defined in `*.wasp` files (for now just one), while the specific parts (web components, back-end queries, ...) are implemented in specific non-Wasp technologies (React, NodeJS, Prisma) and then referenced in the `*.wasp` files.
|
||||
|
||||
Basic structure of a Wasp project is:
|
||||
- `*.wasp` file
|
||||
- The `src/` folder -> Contains non-wasp code (JS, CSS, ...). You can structure it however you want, as long as you put it somewhere inside the correct subfolder:
|
||||
- The `src/` folder -> Contains non-Wasp code (JS, CSS, ...). You can structure it however you want, as long as you put it somewhere inside the correct subfolder:
|
||||
- The `src/server` folder - Contains your server code (i.e., executed by Node JS).
|
||||
- The `src/client` folder - Contains your client code (i.e., executed by JS in user's browsers).
|
||||
- The `src/shared` folder - Contains the code you want to share between the server and the client (e.g., utility functions).
|
||||
|
@ -10,7 +10,7 @@ Let's define a Todo list (luckily we have an app for that now ;)) to get this do
|
||||
- [ ] Add Wasp entity `User`.
|
||||
- [ ] Add `auth` Wasp declaration.
|
||||
- [ ] Add `Login` and `Signup` pages
|
||||
- [ ] Modify `src/client/MainPage.js` so that it requires authentication.
|
||||
- [ ] Modify `src/client/MainPage.jsx` so that it requires authentication.
|
||||
- [ ] Add Prisma relation between `User` and `Task` entities.
|
||||
- [ ] Modify our queries and actions so that they work only with the tasks belonging to the authenticated user.
|
||||
- [ ] Add logout button.
|
||||
@ -68,8 +68,8 @@ during signup, check out the [lower-level auth API](/docs/language/features#lowe
|
||||
Ok, that was easy!
|
||||
|
||||
To recap, so far we have defined:
|
||||
- `User` entity.
|
||||
- `app.auth` field, thanks to which Wasp gives us plenty of auth functionality.
|
||||
- The `User` entity.
|
||||
- The `app.auth` field, thanks to which Wasp gives us plenty of auth-related functionality.
|
||||
|
||||
## Adding Login and Signup pages
|
||||
|
||||
@ -91,8 +91,7 @@ page LoginPage {
|
||||
|
||||
Great, Wasp now knows how to route these and where to find the pages. Now to the React code of the pages:
|
||||
|
||||
```jsx title="src/client/LoginPage.js"
|
||||
import React from 'react'
|
||||
```jsx title="src/client/LoginPage.jsx"
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import LoginForm from '@wasp/auth/forms/Login'
|
||||
@ -114,8 +113,7 @@ export default LoginPage
|
||||
|
||||
The Signup page is very similar to the login one:
|
||||
|
||||
```jsx title="src/client/SignupPage.js"
|
||||
import React from 'react'
|
||||
```jsx title="src/client/SignupPage.jsx"
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
import SignupForm from '@wasp/auth/forms/Signup'
|
||||
@ -146,7 +144,7 @@ There is a specific Wasp feature that allows us to achieve this in a simple way:
|
||||
// ...
|
||||
page MainPage {
|
||||
authRequired: true,
|
||||
component: import Main from "@client/MainPage.js"
|
||||
component: import Main from "@client/MainPage"
|
||||
}
|
||||
```
|
||||
|
||||
@ -155,21 +153,20 @@ If an unauthenticated user tries to access route `/` where our page `MainPage` i
|
||||
|
||||
Also, when `authRequired` is set to `true`, the React component of a page (specified by `component` property within `page`) will be provided `user` object as a prop. It can be accessed like this:
|
||||
|
||||
```jsx {1} title="src/client/MainPage.js"
|
||||
```jsx {1} title="src/client/MainPage.jsx"
|
||||
const MainPage = ({ user }) => {
|
||||
// do something with user
|
||||
// Do something with the user
|
||||
}
|
||||
```
|
||||
|
||||
Ok, time to try out how this works!
|
||||
|
||||
Now, we can again run
|
||||
Now, we can start the app again (if it's not still running):
|
||||
```shell-session
|
||||
wasp start
|
||||
```
|
||||
|
||||
Try going to `/` in our web app. It will now redirect you to `/login`, where you'll be asked to authenticate.
|
||||
Once you log in or sign up, you will be sent back to `/` and you will see the todo list.
|
||||
Try going to the main page (`/`) of our web app. It will now redirect you to `/login`, where you'll be asked to authenticate. Once you log in or sign up, you will be sent back to `/` and you will see the todo list.
|
||||
|
||||
Let's now see how things look in the database! Run:
|
||||
```shell-session
|
||||
@ -262,7 +259,7 @@ Due to how Prisma works, we had to convert `update` to `updateMany` in `updateTa
|
||||
|
||||
Right, that should be it!
|
||||
|
||||
Run
|
||||
Run (or just continue running):
|
||||
```shell-session
|
||||
wasp start
|
||||
```
|
||||
@ -282,7 +279,7 @@ You will see that each user has its own tasks, just as we specified in our code!
|
||||
## Logout button
|
||||
|
||||
Last, but not the least, let's add logout functionality:
|
||||
```jsx {2,10} title="src/client/MainPage.js"
|
||||
```jsx {2,10} title="src/client/MainPage.jsx"
|
||||
// ...
|
||||
import logout from '@wasp/auth/logout.js'
|
||||
//...
|
||||
|
@ -20,7 +20,7 @@ You have just run your app in the development mode!
|
||||
:::
|
||||
|
||||
You will be seeing a lot of different output from the client, server and database setting themselves up.
|
||||
Once ready, a new tab should open in your browser at `http://localhost:3000`, with simple placeholder page:
|
||||
Once ready, a new tab should open in your browser at `http://localhost:3000`, with a simple placeholder page:
|
||||
|
||||
<img alt="Screenshot of new Wasp app"
|
||||
src={useBaseUrl('img/wasp-new-screenshot.png')}
|
||||
@ -39,26 +39,40 @@ Let's inspect the Wasp project we just created:
|
||||
├── main.wasp # Your wasp code goes here.
|
||||
├── src
|
||||
│ ├── client # Your client code (JS/CSS/HTML) goes here.
|
||||
│ │ ├── jsconfig.json
|
||||
│ │ ├── Main.css
|
||||
│ │ ├── MainPage.js
|
||||
│ │ ├── MainPage.jsx
|
||||
│ │ ├── react-app-env.d.ts
|
||||
│ │ ├── tsconfig.json
|
||||
│ │ └── waspLogo.png
|
||||
│ ├── server # Your server code (Node JS) goes here.
|
||||
│ │ └── jsconfig.json
|
||||
│ ├── shared # Your shared code goes here.
|
||||
│ │ └── jsconfig.json
|
||||
│ │ └── tsconfig.json
|
||||
│ ├── shared # Your shared (runtime independent) code goes here .
|
||||
│ │ └── tsconfig.json
|
||||
│ └── .waspignore
|
||||
└── .wasproot
|
||||
```
|
||||
By _your code_, we mean _"the code you write"_ (as opposed to the code generated by Wasp). Wasp expects you to separate all external code into three folders to make it clear which runtime executes what:
|
||||
- `src/server` - Contains the code executed on the server (i.e., in Node)
|
||||
- `src/client` - Contains the code executed on the client (i.e., JS in the browser)
|
||||
- `src/shared` - Contains the code you want to use on both the client and the server (e.g., utility functions)
|
||||
- `src/shared` - Contains the code you want to use on both the client and the server (e.g., runtime-independent utility functions)
|
||||
|
||||
You may be wondering what about the rest of the generated files (`tsconfig.json`
|
||||
and `reat-app-env.d.ts`? Your IDE needs them to improve your development
|
||||
experience (i.e., autocompletion, intellisense, etc.), so it's best to leave
|
||||
them alone (for now).
|
||||
|
||||
:::note Typescript Support
|
||||
We've introduced Typescript support in Beta version 0.7.0, but you are free to use js/jsx or ts/tsx syntax as you see fit.
|
||||
We've introduced Typescript support in Beta version 0.7.0, but you are free to
|
||||
use JavaScript (js/jsx) or TypeScript (ts/tsx) as you see fit, on a file-by-file basis.
|
||||
|
||||
For this tutorial, we will simply use vanilla Javascript and JSX syntax 🍦
|
||||
|
||||
If you'd prefer to follow along using TypeScript:
|
||||
1. Start by changing `MainPage.jsx` to `MainPage.tsx`
|
||||
2. For the rest of the tutorial, whenever you want to use TypeScript in a file,
|
||||
just make sure to use the appropriate extensions.
|
||||
|
||||
No extra configuration needed!
|
||||
:::
|
||||
|
||||
Let's start with the `main.wasp` file, which introduces 3 new concepts:
|
||||
@ -78,15 +92,15 @@ route RootRoute { path: "/", to: MainPage } // Render page MainPage on url `/` (
|
||||
|
||||
page MainPage {
|
||||
// We specify that ReactJS implementation of our page can be found in
|
||||
// `src/client/MainPage.js` as a default export (uses standard js import syntax).
|
||||
// Use '@client' to reference files inside the src/client folder.
|
||||
component: import Main from "@client/MainPage.js"
|
||||
// `src/client/MainPage.jsx` (the extension is not important) as a default
|
||||
// export (uses standard js import syntax). Use '@client' to reference files
|
||||
// inside the src/client folder.
|
||||
component: import Main from "@client/MainPage"
|
||||
}
|
||||
```
|
||||
|
||||
Let's now take a look at that React component we referenced in the `page MainPage { ... }` declaration in `main.wasp`:
|
||||
```jsx title="src/client/MainPage.js"
|
||||
import React from 'react'
|
||||
```jsx title="src/client/MainPage.jsx"
|
||||
import waspLogo from './waspLogo.png'
|
||||
import './Main.css'
|
||||
|
||||
@ -98,7 +112,7 @@ export default MainPage
|
||||
As we can see, this is simply a functional React component which uses the CSS and Wasp logo files sitting next to it in the `src/client` dir.
|
||||
|
||||
This is all the code we need!
|
||||
Wasp quietly takes care of everything else needed to define, build, and run a web app.
|
||||
Wasp quietly takes care of everything else necessary to define, build, and run a web app.
|
||||
|
||||
:::tip
|
||||
`wasp start` automatically picks up the changes you make and restarts the app, so keep it running.
|
||||
@ -107,18 +121,24 @@ Wasp quietly takes care of everything else needed to define, build, and run a we
|
||||
## Cleaning up
|
||||
|
||||
Let's make our first changes!
|
||||
To prepare the clean slate for building the TodoApp, delete the files `Main.css` and `waspLogo.png` from the `src/client/` folder (`src/shared` and `src/server` are already clean). The `jsconfig.json` files are necessary for IDE support, so it's important to keep them. Now that `src/client` contains only `jsconfig.json` and `MainPage.js` let's start by making the `MainPage` component much simpler:
|
||||
|
||||
```jsx title="src/client/MainPage.js"
|
||||
import React from 'react'
|
||||
To prepare the clean slate for building the TodoApp, delete the files `Main.css`
|
||||
and `waspLogo.png` from the `src/client/` folder (`src/shared` and `src/server`
|
||||
are already clean). Wasp needs the `tsconfig.json` and `react-app-env.d.ts` for
|
||||
IDE support, so it's important to keep them.
|
||||
|
||||
Now that `src/client` contains only `tsconfig.json`, `react-app-env.d.ts`, and
|
||||
`MainPage.jsx` let's start by making the `MainPage` component much simpler:
|
||||
|
||||
```jsx title="src/client/MainPage.jsx"
|
||||
const MainPage = () => {
|
||||
return <div> Hello world! </div>
|
||||
}
|
||||
|
||||
export default MainPage
|
||||
```
|
||||
|
||||
At this point, you should be seeing something like
|
||||
At this point, you should see something like this:
|
||||
|
||||
<img alt="Todo App - Hello World"
|
||||
src={useBaseUrl('img/todo-app-hello-world.png')}
|
||||
|
@ -6,7 +6,7 @@ import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
To enable the creation of new tasks, we will need two things:
|
||||
1. A Wasp action that creates a new task.
|
||||
2. A React form that calls that action with the new task's data.
|
||||
2. A React form which calls that action with the new task's data.
|
||||
|
||||
## Action
|
||||
Creating an action is very similar to creating a query.
|
||||
@ -18,7 +18,7 @@ First we declare the action in Wasp:
|
||||
// ...
|
||||
|
||||
action createTask {
|
||||
fn: import { createTask } from "@server/actions.js",
|
||||
fn: import { createTask } from "@server/actions",
|
||||
entities: [Task]
|
||||
}
|
||||
```
|
||||
@ -40,9 +40,7 @@ We put the JS function in a new file `src/server/actions.js`, but we could have
|
||||
|
||||
## React form
|
||||
|
||||
```jsx {5,12,39-61} title="src/client/MainPage.js"
|
||||
import React from 'react'
|
||||
|
||||
```jsx {3,10,37-59} title="src/client/MainPage.jsx"
|
||||
import { useQuery } from '@wasp/queries'
|
||||
import getTasks from '@wasp/queries/getTasks'
|
||||
import createTask from '@wasp/actions/createTask'
|
||||
@ -123,5 +121,5 @@ You will notice that when you create a new task, the list of tasks is automatica
|
||||
The reason why the `getTasks` query automatically updates when the `createTask` action is executed is because Wasp is aware that both of them are working with the `Task` entity, and therefore assumes that action that operates on `Task` (in this case `createTask`) might have changed the result of the `getTasks` query. Therefore, in the background, Wasp nudges the `getTasks` query to update. This means that **out of the box, Wasp will make sure that all your queries that deal with entities are always in sync with any changes that the actions might have done**.
|
||||
|
||||
:::note
|
||||
While this kind of approach to automatic invalidation of queries is very convenient, it is in some situations wasteful and could become a performance bottleneck as an app grows. In that case, you will be able to override this default behaviour and instead provide more detailed (and performant) instructions on how the specific action should affect queries. This is not yet implemented, but is something we plan to do and you can track the progress [here](https://github.com/wasp-lang/wasp/issues/63) (or even contribute!).
|
||||
While this kind of approach to automatic invalidation of queries is very convenient, it is in some situations wasteful and could become a performance bottleneck as the app grows. In that case, you will be able to override this default behaviour and instead provide more detailed (and performant) instructions on how the specific action should affect queries. This is not yet implemented, but is something we plan to do and you can track the progress [here](https://github.com/wasp-lang/wasp/issues/63) (or even contribute!).
|
||||
:::
|
||||
|
@ -4,7 +4,7 @@ title: "Dependencies"
|
||||
|
||||
import useBaseUrl from '@docusaurus/useBaseUrl';
|
||||
|
||||
What is a Todo app without some clocks!? Well, still a Todo app, but certainly not as fun as one with the clocks!
|
||||
What is a Todo app without some clocks!? Well, still a Todo app, but certainly not as fun as one with clocks!
|
||||
|
||||
So, let's add a couple of clocks to our app, to help us track time while we perform our tasks (and to demonstrate the `app.dependencies` feature).
|
||||
|
||||
@ -26,12 +26,12 @@ wasp start
|
||||
to have Wasp download and install the new dependency. If `wasp start` is already running, Wasp will detect the dependency change, and restart automatically.
|
||||
|
||||
Next, let's create a new component `Clocks` where we can play with the clocks.
|
||||
```jsx title="src/client/Clocks.js"
|
||||
import React, { useEffect, useState } from 'react'
|
||||
```jsx title="src/client/Clocks.jsx"
|
||||
import { useEffect, useState } from 'react'
|
||||
import Clock from 'react-clock'
|
||||
import 'react-clock/dist/Clock.css'
|
||||
|
||||
export default () => {
|
||||
const Clocks = () => {
|
||||
const [time, setTime] = useState(new Date())
|
||||
|
||||
useEffect(() => {
|
||||
@ -46,10 +46,12 @@ export default () => {
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Clocks
|
||||
```
|
||||
|
||||
And let's import it in our main React component.
|
||||
```jsx {2,11} title="src/client/MainPage.js"
|
||||
```jsx {2,11} title="src/client/MainPage.jsx"
|
||||
// ...
|
||||
import Clocks from './Clocks'
|
||||
|
||||
@ -68,6 +70,6 @@ const MainPage = () => {
|
||||
}
|
||||
// ...
|
||||
```
|
||||
As you can see, importing other files from `src/client` is completely normal, just use the relative path. The same goes for all files under `src/server`. You can't (and shouldn't) import files from `src/client` into `src/server` and vice versa. If you want to share code between the two runtimes, you can use a relative import to import anything from `src/shared` into both the client code and the server code.
|
||||
As you can see, importing other files from `src/client` is completely normal, just use the relative path. The same goes for all files under `src/server`. You can't (and shouldn't) import files from `src/client` into `src/server` and vice versa. If you want to share code between the two runtimes, you can use a relative import to import anything from `src/shared` into both the client code and the server code. The `src/shared` is especially handy for full-stack type definitions when using TypeScript.
|
||||
|
||||
That is it! We added a dependency and used it in our project.
|
||||
|
@ -14,7 +14,7 @@ Queries are here when we need to fetch/read something, while actions are here wh
|
||||
We will start with writing a query, since we are just listing tasks and not modifying anything for now.
|
||||
|
||||
To list tasks, we will need two things:
|
||||
1. Wasp query that fetches all the tasks from the database.
|
||||
1. A Wasp query that fetches all the tasks from the database.
|
||||
2. React logic that calls our query and displays its results.
|
||||
|
||||
## Wasp query
|
||||
@ -31,7 +31,7 @@ query getTasks {
|
||||
// We specify that JS implementation of the query (which is an async JS function)
|
||||
// can be found in `src/server/queries.js` as the named export `getTasks`.
|
||||
// Use '@server' to reference files inside the src/server folder.
|
||||
fn: import { getTasks } from "@server/queries.js",
|
||||
fn: import { getTasks } from "@server/queries",
|
||||
// We tell Wasp that this query is doing something with entity `Task`. With that, Wasp will
|
||||
// automatically refresh the results of this query when tasks change.
|
||||
entities: [Task]
|
||||
@ -52,7 +52,7 @@ Query function parameters:
|
||||
- `context`: `object`, additional stuff provided by Wasp.
|
||||
|
||||
|
||||
Since we declared in `main.wasp` that our query uses entity Task, Wasp injected a [Prisma client](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud) for entity Task as `context.entities.Task` - we used it above to fetch all the tasks from the database.
|
||||
Since we declared in `main.wasp` that our query uses entity `Task`, Wasp injected a [Prisma client](https://www.prisma.io/docs/reference/tools-and-interfaces/prisma-client/crud) for the `Task` entity as `context.entities.Task` - we used it above to fetch all the tasks from the database.
|
||||
|
||||
:::info
|
||||
Queries and actions are NodeJS functions that are executed on the server. Therefore, we put them in the `src/server` folder.
|
||||
@ -62,9 +62,7 @@ Queries and actions are NodeJS functions that are executed on the server. Theref
|
||||
|
||||
We've just said that the queries we write are executed on the server, but Wasp will generate client-side query functions (taking care of the network and cache invalidation in the background). Let's finally use the query we've just created, `getTasks`, in our React component to list the tasks:
|
||||
|
||||
```jsx {3-4,7-16,19-32} title="src/client/MainPage.js"
|
||||
import React from 'react'
|
||||
|
||||
```jsx {1-2,5-14,17-32} title="src/client/MainPage.jsx"
|
||||
import getTasks from '@wasp/queries/getTasks'
|
||||
import { useQuery } from '@wasp/queries'
|
||||
|
||||
@ -105,7 +103,7 @@ Most of this is just regular React, the only exception being two special `@wasp`
|
||||
- `import getTasks from '@wasp/queries/getTasks'` - Gives us our freshly defined Wasp query.
|
||||
- `import { useQuery } from '@wasp/queries'` - Gives us Wasp's [useQuery](language/features.md#the-usequery-hook) React hook which is actually just a thin wrapper over [react-query](https://github.com/tannerlinsley/react-query)'s [useQuery](https://react-query.tanstack.com/docs/guides/queries) hook, behaving very similarly while offering some extra integration with Wasp.
|
||||
|
||||
While we could call query directly as `getTasks()`, calling it as `useQuery(getTasks)` gives us reactivity- the React component gets re-rendered if the result of the query changes.
|
||||
While we could directly call the query with `getTasks()`, calling it with `useQuery(getTasks)` gives us reactivity - the React component gets re-rendered if the result of the query changes.
|
||||
|
||||
With these changes, you should be seeing the text "No tasks" on the screen:
|
||||
|
||||
|
@ -19,14 +19,13 @@ If you are interested in what is Wasp actually generating in the background, you
|
||||
|
||||
We introduced Tailwind CSS support in our Beta version 0.7.0! 🎨
|
||||
|
||||
If you want to style your app with sweet tailwind css classes, all you have to do is add a couple config files to your app. Check out [this guide](https://www.wasp-lang.dev/blog/2022/11/16/tailwind-feature-announcement)
|
||||
) to learn more!
|
||||
If you want to style your app with sweet tailwind css classes, all you have to do is add a couple config files to your app. Check out [this guide](https://www.wasp-lang.dev/blog/2022/11/16/tailwind-feature-announcement) to learn more!
|
||||
|
||||
## Where next?
|
||||
|
||||
Well, you could check [the "Language" section](language/overview.md) of the docs for more details on specific parts of Wasp.
|
||||
Or, you could try to build something on your own with Wasp!
|
||||
Or, you could use Wasp to build something of your own!
|
||||
|
||||
If you find some features you would like to have are missing, or have any other kind of feedback, please write to us on [Discord](https://discord.gg/rzdnErX) or create an issue on [Github](https://github.com/wasp-lang/wasp), so we can learn which features to add/improve next.
|
||||
If you notice that some of the features you'd like to have are missing, or have any other kind of feedback, please write to us on [Discord](https://discord.gg/rzdnErX) or create an issue on [Github](https://github.com/wasp-lang/wasp), so we can learn which features to add/improve next.
|
||||
Even better, if you would like to contribute or help building the feature, let us know!
|
||||
You can find more details on contributing [here](contributing.md).
|
||||
|
@ -19,7 +19,7 @@ We declare a Wasp action:
|
||||
// ...
|
||||
|
||||
action updateTask {
|
||||
fn: import { updateTask } from "@server/actions.js",
|
||||
fn: import { updateTask } from "@server/actions",
|
||||
entities: [Task]
|
||||
}
|
||||
```
|
||||
@ -43,7 +43,7 @@ export const updateTask = async (args, context) => {
|
||||
## React logic
|
||||
|
||||
And we update the React component:
|
||||
```jsx {2,7-16,23} title="src/client/MainPage.js"
|
||||
```jsx {2,7-16,23} title="src/client/MainPage.jsx"
|
||||
// ...
|
||||
import updateTask from '@wasp/actions/updateTask'
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 77 KiB |
Loading…
Reference in New Issue
Block a user