The translator sequence

Bitter JS ecology has been for a long time. After 2020, I have been wandering about what kind of technology stack to build for myself, and there are not many practical achievements. There are too many libraries, and a small scene can have many solutions. Direction is too much, even if the big front three words, can be split into a lot of subdivisions. Until I met Rome, I saw that the community was trying to integrate and rebuild the front-end tool chain of the Node ecosystem. Deno gets rid of Node’s legacy and builds a JavaScript/TypeScript runtime that is closer to modern standards. Now comes Blitz.js, a one-stop React full-stack framework with more backend scenarios on top of Next-js… Many of my directionality problems then became clear: by creating different REPos to specialize in different directions, each Repo could be representative and cover more scenarios. So –

  • Deno research techniques: look at the future, learn standards. Node doesn’t drop.
  • Deno algorithm tour: Brush algorithms, play tests.
  • ECMAScript Interview Guide: Prepare the interview, lay the foundation;
  • Blitz. Js + React Full stack Development Manual: Do engineering, try full stack. React, Next-js, back-end development.
  • And the repo of all pits……

Of course, blitz. js also has a number of attractive features:

  • All-in-one full stack architecture: Everything from database to client is done in one Monorepo without repetitive code. If you like TypeScript, you’ll probably have to write two sets of the same TS… And this kind of one-piece architecture makes it easy for my project inspiration to quickly fall into place from beginning to end.
  • Apis are no longer necessary: REST and GraphQL? Perhaps none of it is needed, and blitz. js is built at compile time. When an API needs to be provided for more parties to use, the API can be generated in combination with related libraries.
  • Easier out of the box experience: Scaffolding provides login, registration and even password reset functions directly after initialization, directly supports the most basic back-end environment, the experience is no better out of the box, and can even be quickly integrated into the mainstream libraries in the ecosystem through the powerful Blitz Generate CLI.
  • Blitz. Js is built on a variety of mainstream ecosystems, based on libraries such as Next. Js, React front end, Prisma back end, etc.
  • Embrace the future: Blitz.js is expected to release v1.0 next month (April 2021).

This article is part of the Blitz.js + React Full-stack Development Manual series. The original translation will be updated to the Blitz.js Chinese repository. Welcome to Star and Watch: github.com/hylerrix/bl… .

Quick start

Configure your environment

You’ll need to use Node 12 or later.

Install the Blitz

Run NPM install -g blitz

Create a new project

  1. blitz new myAppName
  2. cd myAppName
  3. blitz dev
  4. Visit your new project at http://localhost:3000

Welcome to the Blitz community πŸ‘‹

Blitz is a warm, safe, diverse, inclusive and fun community! LGBTQ+, girls and minorities welcome you.

Join our Discord community, where we help everyone build Blitz apps. It’s also an important place for us to collaborate on Blitz itself.

For questions and discussions that take longer, you can post to our forum.

For a complete introduction, read how the Community works. It includes detailed instructions on how to get help, how to report errors, and how to suggest new features.

Welcome your help to make Blitz better! 🀝

We have a great community working together to make Blitz the best framework in the world.

How you can help:

  1. Feedback bugs by submitting an issue on GitHub.
  2. Contributing code: Read the contributing guide to see how to get started.
  3. Sponsorships & donations can start at $5/ month.
  4. And any other way you can! Any contributions (documents, videos, blogs, etc.) are greatly appreciated. If you run into any obstacles, feel free to join us on Discord! πŸ™‚

The next step

The tutorial

The tutorial is a complete exercise in all the basics of Blitz, including adding models to the database and reading and updating data from the front end.

learning

Here are the key Blitz concepts you’ll want to be familiar with:

  • How to create a new page
  • How do I use the file routing system
  • How do I set up and use the database
  • How to use Blitz Queries and Mutations to read and write your database.
  • How to useblitz generateCommand to generate database models with scaffolding

All of the code.

Introductory tutorial

In this tutorial, we will guide you through creating a simple voting system.

We will assume that you have Blitz installed. You can determine whether Blitz is installed or check the installed version number by running the following command on the terminal:

blitz -v
Copy the code

If Blitz has been installed successfully, you should be able to see the installed version number. Otherwise you’ll get an error message like “Command not found: blitz”.

Create a new application

On the command line, after CD goes to the root folder where you want to create the application, run the following command:

blitz new my-blitz-app
Copy the code

Blitz will create a my-Blitz-app folder in your current folder. You will then be prompted to select a form library. The Recommended React Final Form library will be chosen for this tutorial.

Let’s look at what the blitz new command creates:

My - blitz - app β”œ ─ ─ app / β”‚ β”œ ─ ─ the API / β”‚ β”œ ─ ─ auth / β”‚ β”‚ β”œ ─ ─ the components / β”‚ β”‚ β”‚ β”œ ─ ─ LoginForm. The TSX β”‚ β”‚ β”‚ β”” ─ ─ SignupForm. The TSX β”‚ β”‚ β”œ ─ ─ mutations / β”‚ β”‚ β”‚ β”œ ─ ─ changePassword. Ts β”‚ β”‚ β”‚ β”œ ─ ─ forgotPassword. Test. The ts β”‚ β”‚ β”‚ β”œ ─ ─ forgotPassword. Ts β”‚ β”‚ β”‚ β”œ ─ ─ Login. Ts β”‚ β”‚ β”‚ β”œ ─ ─ logout. Ts β”‚ β”‚ β”‚ β”œ ─ ─ resetPassword. Test. The ts β”‚ β”‚ β”‚ β”œ ─ ─ resetPassword. Ts β”‚ β”‚ β”‚ β”” ─ ─ signup. Ts β”‚ β”‚ β”œ ─ ─ Pages / β”‚ β”‚ β”‚ β”œ ─ ─ forgot - password. TSX β”‚ β”‚ β”‚ β”œ ─ ─ the login. The TSX β”‚ β”‚ β”‚ β”œ ─ ─ reset - password. TSX β”‚ β”‚ β”‚ β”” ─ ─ signup. The TSX β”‚ β”‚ β”” ─ ─ Validations. Ts β”‚ β”œ ─ ─ the core / β”‚ β”‚ β”œ ─ ─ the components / β”‚ β”‚ β”‚ β”œ ─ ─ Form. The TSX β”‚ β”‚ β”‚ β”” ─ ─ LabeledTextField. The TSX β”‚ β”‚ β”œ ─ ─ hooks / β”‚ β”‚ β”‚ β”” ─ ─ useCurrentUser. Ts β”‚ β”‚ β”” ─ ─ layouts / β”‚ β”‚ β”” ─ ─ Layout. The TSX β”‚ β”œ ─ ─ pages / β”‚ β”‚ β”œ ─ ─ 404. The TSX β”‚ β”‚ β”œ ─ ─_app.tsX β”‚ β”œβ”€ _Document. The TSX β”‚ β”‚ β”œ ─ ─ index. The test. The TSX β”‚ β”‚ β”” ─ ─ index. The TSX β”‚ β”” ─ ─ the users / β”‚ β”” ─ ─ the queries / β”‚ β”” ─ ─ getCurrentUser. Ts β”œ ─ ─ db / β”‚ β”œ ─ ─ Index. Ts β”‚ β”œ ─ ─ schema. Prisma β”‚ β”” ─ ─ seeds. The ts β”œ ─ ─ integrations / β”œ ─ ─ mailers / β”‚ β”” ─ ─ forgotPasswordMailer. Ts β”œ ─ ─ public / β”‚ β”œ ─ ─ the favicon. Ico* β”‚ β”” ─ ─ logo. PNG β”œ ─ ─ the test / β”‚ β”œ ─ ─ setup. Ts β”‚ β”” ─ ─ utils. The TSX β”œ ─ ─ the README. Md β”œ ─ ─ Babel. Config. Js β”œ ─ ─ blitz. Config. Js β”œ ─ ─ β”œβ”€β”€ class β”œβ”€β”€ class, class, class, class, class, class, class, class, classCopy the code

The above documents are:

  • app/Folders are containers for most of the functionality in a project. You can place any page or API route here.
  • app/pages/Folders are the primary page folders. If you’ve used next.js you’ll notice the difference immediately. In Blitz, you can have manypagesFolders, which will be merged at build time.
  • app/core/The folder is the primary location for generic components, Hooks, and so on that are used throughout the application.
  • db/Is where the database configuration is located. Come here if you are writing models or checking migrations.
  • public/Folders allow you to place any static resource. If you have images, files or videos that you want to use in your app, you can put them in there.
  • .babelrc.js,.envEtc. (” dotFiles files “) are the configuration files used by various JavaScript tools.
  • blitz.config.jsAdvanced custom configuration for Blitz, withnext.config.jsThe same
  • tsconfig.jsonIs our recommended TypeScript setting.

Development environment server

Now, if you’re not already in the my-blitz-app folder, make sure you switch to it and run the following command:

blitz dev
Copy the code

You should see the following output on the command line:

➀ Env from /private/ TMP /my-blitz-app/. Env warn - You have enabled experimental feature(s).warn - Experimental features are not covered by semver, and may cause unexpected or broken application behavior. Use them at your own risk. ready - started server on 0.0.0.0:3000, url: http://localhost:3000 info - Using external babel configuration from /my-blitz-app/babel.config.js event - compiled successfullyCopy the code

Now that the server is running successfully, access localhost:3000 in your browser. You’ll see a welcome page with the Blitz logo. Success!

Register as a user

The Bliz app lets users log in and register right out of the box! Now let’s try it. Click the Register button, enter any email and password, then click Create Account and you will be redirected to the user home page where you can see your user ID and role.

You can also try logging out and logging back in if necessary. Or click Forget password on the login page to try to reset the password process.

Write your first page

Let’s create your first page.

Open the app/pages/index.tsx file and replace all the contents of the Home component with this code:

/ /...

const Home: BlitzPage = () = > {
  return (
    <div>
      <h1>Hello, world!</h1>

      <Suspense fallback="Loading...">
        <UserInfo />
      </Suspense>
    </div>)}/ /...
Copy the code

After you save the file you will see the browser page updated. You can add as many customizations as you want. When you are ready, go to the next section.

Database Configuration

The good news is that you have an SQLite database set up for you! You can run Blitz Prisma Studio in a terminal to open a Web interface that allows you to view database data.

Note that you may want to use a more scalable database (such as PostgreSQL) when starting your first real project to avoid the hassle of switching databases in the future. For more information, see Database Overview. For now, we’ll stick with the default SQLite database.

Scaffolding code for the model

Blitz provides a convenient CLI command called generate to build boilerplate code. We will use Generate to create two models: Question and Choice. A Question contains the content of the Question and a list of choices. Choice contains the Choice, vote count, and related questions. Blitz will automatically generate an ID, a creation timestamp, and a latest update timestamp for both models.

First, we will generate thetaQuestionAll information about the model:

blitz generate all question text:string
Copy the code

When prompted, press Enter to run Prisma Migrate, which will update your database schema with the new model. A name is required, and you can enter a value like “add question.”

CREATE app/pages/questions/[questionId].tsx CREATE app/pages/questions/[questionId]/edit.tsx CREATE app/pages/questions/index.tsx CREATE app/pages/questions/new.tsx CREATE app/questions/components/QuestionForm.tsx CREATE  app/questions/queries/getQuestion.ts CREATE app/questions/queries/getQuestions.ts CREATE app/questions/mutations/createQuestion.ts CREATE app/questions/mutations/deleteQuestion.ts CREATE App/questions/mutations updateQuestion. Ts βœ” Modelfor 'question' created in schema.prisma:

> model Question {
>   id        Int      @default(autoincrement()) @id
>   createdAt DateTime @default(now())
>   updatedAt DateTime @updatedAt
>   text      String
> }

? Run 'prisma migrate dev'to update your database? (Y/n) holdstrue
Copy the code
Environment variables loaded from .env
Prisma schema loaded from db/schema.prisma
Datasource "db": SQLite database "db.sqlite" at "file:./db.sqlite"➀ Name of migration... add question The following migration(s) have been created and applied from new schema changes: β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 / β”œ ─ 088 ./node_modules/@prisma/clientin 103ms

Everything is now in sync.
Copy the code

The generate command with the all type generates the associated model, queries, mutation, and page files. See the Blitz Generate page for more available type options.

Next, we will generate the results with the corresponding queries and mutationsChoiceModel.

This time we use the Resource type because we don’t need to generate pages for our Choice model:

blitz generate resource choice text votes:int:default=0 belongsTo:question
Copy the code

Similarly, when prompted to migrate, press Enter and Enter the name of the migration.

CREATE app/choices/queries/getChoice.ts CREATE app/choices/queries/getChoices.ts CREATE app/choices/mutations/createChoice.ts CREATE app/choices/mutations/deleteChoice.ts CREATE App/choices/mutations updateChoice. Ts βœ” Modelfor 'choice' created in schema.prisma:

> model Choice {
>   id         Int      @default(autoincrement()) @id
>   createdAt  DateTime @default(now())
>   updatedAt  DateTime @updatedAt
>   text       String
>   votes      Int      @default(0)
>   question   Question @relation(fields: [questionId], references: [id])
>   questionId Int
> }

? Run 'prisma migrate dev'to update your database? (Y/n) holdstrue
Copy the code

Finally, let’s updateQuestionModel to associate withChoiceOn.

Open DB /schema.prisma and add Choices Choice to the Question model [].

model Question {
  id        Int      @id @default(autoincrement())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
  text      String
+ choices Choice[]
}
Copy the code

Then run Blitz Prisma Generate to update the PRISma client to synchronize the schema changes. There is no database migration involved because there are no actual fields added to the Question model from the database.

Access the Prisma database client

Now, let’s jump into the Blitz interactive Shell and use the Primsa database client that Blitz provides for you. To start the Blitz console, use the following command:

blitz console
Copy the code

Once you are in the console, browse the database client:

# No questions are in the system yet.⚑ > await db.question.findmany () []# Create a new Question:
⚑ > let q = await db.question.create({data: {text: "What's new?"}})
undefined

# See the entire object:⚑ > q {id: 1, createdAt: 2020-06-15T15:06:14.959z, updatedAt: 2020-06-15T15:06:14.959z, text:"What's new?"
}

# Or, access individual values on the object:⚑ > q.t ext"What's new?"

# Change values by using the update function:
⚑ > q = await db.question.update({where: {id: 1}, data: {text: "What's up?"}}) {id: 1, createdAt: 2020-06-15T15:06:14.959z, updatedAt: 2020-06-15T15:13:17.394z, text:"What's up?"
}

# db.question.findMany() now displays all the questions in the database:⚑ > await db.question.findmany () [{id: 1, createdAt: 2020-06-15t15:06:14.959z, updatedAt: The T15:2020-06-15 ". 394 z, text:"What's up?"}]Copy the code

Update the code generated by model properties

Before running the application again, we need to customize some of the generated code. Eventually these fixes will no longer be needed — but for now, we need to address the resulting unresolved issues.

Automatically generated pages that do not currently use the properties of the actual model that you defined during the build process. It will be supported later, but for now, we need to manually fix the generated pages.

Question page

Enter the app/pages/questions/index. The TSX. Note that a QuestionsList component has been generated for you:

// app/pages/questions/index.tsx

export const QuestionsList = () = > {
  const router = useRouter()
  const page = Number(router.query.page) || 0
  const [{questions, hasMore}] = usePaginatedQuery(getQuestions, {
    orderBy: {id: "asc"},
    skip: ITEMS_PER_PAGE * page,
    take: ITEMS_PER_PAGE,
  })

  const goToPreviousPage = () = > router.push({query: {page: page - 1}})
  const goToNextPage = () = > router.push({query: {page: page + 1}})

  return (
    <div>
      <ul>
        {questions.map((question) => (
          <li key={question.id}>
            <Link href={` /questions/ ${question.id} `} >
              <a>{question.name}</a>
            </Link>
          </li>
        ))}
      </ul>

      <button disabled={page= = =0} onClick={goToPreviousPage}>
        Previous
      </button>
      <button disabled={! hasMore} onClick={goToNextPage}>
        Next
      </button>
    </div>)}Copy the code

But it’s not going anywhere right now! Remember that the Question model we created does not have any “name” fields on it. To solve this problem, replace question. Name with question. Text.

// app/pages/questions/index.tsx

export const QuestionsList = () => {
  const router = useRouter()
  const page = Number(router.query.page) || 0
  const [{questions, hasMore}] = usePaginatedQuery(getQuestions, {
    orderBy: {id: "asc"},
    skip: ITEMS_PER_PAGE * page,
    take: ITEMS_PER_PAGE,
  })

  const goToPreviousPage = () => router.push({query: {page: page - 1}})
  const goToNextPage = () => router.push({query: {page: page + 1}})

  return (
    <div>
      <ul>
        {questions.map((question) => (
          <li key={question.id}>
            <Link href={`/questions/${question.id}`}>
- {question.name}
+ {question.text}
            </Link>
          </li>
        ))}
      </ul>

      <button disabled={page === 0} onClick={goToPreviousPage}>Previous </button> <button disabled={! hasMore} onClick={goToNextPage}> Next </button> </div> ) }Copy the code

Next, we will similar repair methods used in app/questions/components/QuestionForm TSX. In the form submission, change the name in the LabeledTextField to text.

export function QuestionForm<S extends z.ZodType<any, any>>( props: FormProps<S>, ) { return ( <Form<S> {... props}>- 
      
+ 
      
    </Form>
  )
}
Copy the code

createQuestion mutation

In app/questions/mutations createQuestion. Ts, we need to update the createQuestion zod authentication mode, use the text instead of the name.

// app/questions/mutations/createQuestion.ts

const CreateQuestion = z
  .object({
- name: z.string(),
+ text: z.string(),
  })
  .nonstrict()
// ...
Copy the code

updateQuestion mutation

In app/questions/mutations updateQuestion. Ts, we need to update the updateQuestion zod authentication mode, use the text instead of the name.

// app/questions/mutations/updateQuestion.ts

const UpdateQuestion = z
  .object({
    id: z.number(),
- name: z.string(),
+ text: z.string(),
  })
  .nonstrict()
// ...
Copy the code

deleteQuestion mutation

Prisma does not yet support cascading deletion. In the context of this tutorial, this means that it deletes a Question without deleting the associated Choice data. We need to temporarily change the generated deleteQuestion mutation to do this manually. In the text in the edit box to open the app/questions/mutations/deleteQuestion ts and add the following to the top part of the function body.

await db.choice.deleteMany({where: {questionId: id}})
Copy the code

The final result should be:

// app/questions/mutations/deleteQuestion.ts

export default resolver.pipe(
  resolver.zod(DeleteQuestion),
  resolver.authorize(),
  async ({id}) => {
+ await db.choice.deleteMany({where: {questionId: id}})
    const question = await db.question.deleteMany({where: {id}})

    return question
  },
)
Copy the code

Now, this mutation will remove the selection associated with the problem before deleting the problem itself.

Now try creating, updating, and deleting problems

That’s great! Now make sure your program works. Otherwise execute Blitz dev in your terminal and go to localhost:3000/questions. Try to create problems and edit and delete them.

Add options to the question table

You’ve done a great job so far! The next thing we need to do is add choices to our questions. In your editor to open the app/questions/components/QuestionForm TSX.

Add three additional

components as options.

export function QuestionForm<S extends z.ZodType<any, any>>( props: FormProps<S>, ) { return ( <Form<S> {... props}> <LabeledTextField name="text" label="Text" placeholder="Text" />+ 
      
+ 
      
+ 
      
    </Form>
  )
}
Copy the code

Now open the app/pages/questions/new TSX and set initialValues as below:

      <QuestionForm
        submitText="Create Question"
- // initialValues={{ }}
+ initialValues={{choices: []}}
        onSubmit={async (values) => {
          try {
            const question = await createQuestionMutation(values)
            router.push(`/questions/${question.id}`)
          } catch (error) {
            console.error(error)
            return {
              [FORM_ERROR]: error.toString(),
            }
          }
        }}
      />
Copy the code

Then open the app/questions/mutations/createQuestion ts and update zod mode, to allow the mutation receive choice data. And we also need to update the db.question. Create call so that Choice can also be created.

// app/questions/mutations/createQuestion.ts

const CreateQuestion = z
  .object({
    text: z.string(),
+ choices: z.array(z.object({text: z.string()})),
  })
  .nonstrict()

export default resolver.pipe(
  resolver.zod(CreateQuestion),
  resolver.authorize(),
  async (input) => {
- const question = await db.question.create({data: input})
+ const question = await db.question.create({
+ data: {
+... input,
+ choices: {create: input.choices},
+},
+})

    return question
  },
)
Copy the code

Give it a try

Now you can go to localhost: 3000 / questions/new and create a selection with the new problems!

List the choice

It’s time to relax. Go back to localhost:3000/questions in your browser and view all the questions you created. How about we list the relevant choices under these questions? First, we need to customize the question query function. In Prisma, you need to manually let the client know which nesting relationships you need to query by changing your getQuestion. Ts and getQuestions. Ts files to look like this:

// app/questions/queries/getQuestion.ts

const GetQuestion = z.object({
  // This accepts type of undefined, but is required at runtime
  id: z.number().optional().refine(Boolean, "Required"),
})

export default resolver.pipe(
  resolver.zod(GetQuestion),
  resolver.authorize(),
  async ({id}) => {
- const question = await db.question.findFirst({where: {id}})
+ const question = await db.question.findFirst({
+ where: {id},
+ include: {choices: true},
+})if (! question) throw new NotFoundError() return question }, )Copy the code
// app/questions/queries/getQuestions.ts interface GetQuestionsInput extends Pick< Prisma.QuestionFindManyArgs, "where" | "orderBy" | "skip" | "take" > {} export default resolver.pipe( resolver.authorize(), async ({where, orderBy, skip = 0, take = 100}: GetQuestionsInput) => { const {items: questions, hasMore, nextPage, count} = await paginate({ skip, take, count: () => db.question.count({where}), query: (paginateArgs) => db.question.findMany({ ... paginateArgs, where, orderBy,+ include: {choices: true},
        }),
    })

    return {
      questions,
      nextPage,
      hasMore,
      count,
    }
  },
)
Copy the code

Now jump back to our main Question in the browser page (app/pages/questions/index. The TSX), we can list the choice of each Question. Add this code to our List under Link:

// app/pages/questions/index.tsx

// ...
{
  questions.map((question) => (
    <li key={question.id}>
      <Link href={`/questions/${question.id}`}>
        <a>{question.text}</a>
      </Link>
+ 
      
    + {question.choices.map((choice) => ( +
  • + {choice.text} - {choice.votes} votes + +))} + </li> )) } // ... Copy the code

    Now access the /questions route in your browser. Amazing!!!!

    Let’s allow users to vote on these issues!

    Open app/pages/questions/[questionId].tsx in your browser. First, we’ll make some changes to the page.

    1. replace<h1>Question {question.id}</h1> δΈΊ <h1>{question.text}</h1>.
    2. deletepreElement, and copy the following into the selection list you wrote earlier:
    <ul>
      {question.choices.map((choice) = > (
        <li key={choice.id}>
          {choice.text} - {choice.votes} votes
        </li>
      ))}
    </ul>
    Copy the code

    If you go back to the browser, the page currently looks like this!

    Now it’s time to add voting!

    First of all, we need to open the app/choices/mutations/updateChoice ts, update zod mode, add new voting function.

    const UpdateChoice = z
      .object({
        id: z.number(),
    - name: z.string(),}) .nonstrict() export default resolver.pipe( resolver.zod(UpdateChoice), resolver.authorize(), async ({id, ... data}) => {- const choice = await db.choice.update({where: {id}, data})
    + const choice = await db.choice.update({
    + where: {id},
    + data: {votes: {increment: 1}},
    +})
    
        return choice
      },
    )
    Copy the code

    Return to app/pages/questions/[questionId].tsx and make the following changes:

    In our li, add the following button:

    <li key={choice.id}>
      {choice.text} - {choice.votes} votes
      <button>Vote</button>
    </li>
    Copy the code

    Next, import our updated updateChoice mutation and create the handleVote function on the page.

    // app/pages/questions/[questionId].tsx
    +import updateChoice from "app/choices/mutations/updateChoice"/ /... export const Question = () => { const router = useRouter() const questionId = useParam("questionId", "number") const [deleteQuestionMutation] = useMutation(deleteQuestion) const [question] = useQuery(getQuestion, {id: questionId})+ const [updateChoiceMutation] = useMutation(updateChoice)
    +
    + const handleVote = async (id: number) => {
    + try {
    + await updateChoiceMutation({id})
    + refetch()
    + } catch (error) {
    + alert("Error updating choice " + JSON.stringify(error, null, 2))
    +}
    +}
    
      return (
    Copy the code

    We then need to update the useQuery call related to the problem to return the refetch function that needs to be used inside handleVote.

    // app/pages/questions/[questionId].tsx
    
    //...
    - const [question] = useQuery(getQuestion, {id: questionId})
    + const [question, {refetch}] = useQuery(getQuestion, {id: questionId})/ /...Copy the code

    Finally, we’ll tell the new button to use this function!

    <button onClick={() = > handleVote(choice.id)}>Vote</button>
    Copy the code

    The final Question component should look like this:

    export const Question = () = > {
      const router = useRouter()
      const questionId = useParam("questionId"."number")
      const [deleteQuestionMutation] = useMutation(deleteQuestion)
      const [question, {refetch}] = useQuery(getQuestion, {id: questionId})
      const [updateChoiceMutation] = useMutation(updateChoice)
    
      const handleVote = async (id: number) => {
        try {
          await updateChoiceMutation({id})
          refetch()
        } catch (error) {
          alert("Error updating choice " + JSON.stringify(error, null.2))}}return (
        <>
          <Head>
            <title>Question {question.id}</title>
          </Head>
    
          <div>
            <h1>{question.text}</h1>
            <ul>
              {question.choices.map((choice) => (
                <li key={choice.id}>
                  {choice.text} - {choice.votes} votes
                  <button onClick={()= > handleVote(choice.id)}>Vote</button>
                </li>
              ))}
            </ul>
    
            <Link href={` /questions/ ${question.id} /edit`} >
              <a>Edit</a>
            </Link>
    
            <button
              type="button"
              onClick={async() = >{ if (window.confirm("This will be deleted")) { await deleteQuestionMutation({id: Question. Id}) router.push("/questions")}}} style={{marginLeft: "0.5rem"}} > Delete</button>
          </div>
        </>)}Copy the code

    Finally, let’s support editing a choice under a question

    If you click the Edit button on one of the existing questions, you will see that it uses the same form as creating the question. At this point, the section is complete! We just need to update our mutation.

    Open the app/questions/mutations updateQuestion. Ts and make the following changes:

    // app/questions/mutations/updateQuestion.ts import {resolver} from "blitz" import db from "db" import * as z from "zod"  const UpdateQuestion = z .object({ id: z.number(), text: z.string(),+ choices: z.array(
    + z.object({id: z.number().optional(), text: z.string()}).nonstrict(),
    +),}) .nonstrict() export default resolver.pipe( resolver.zod(UpdateQuestion), resolver.authorize(), async ({id, ... data}) => {- const question = await db.question.update({where: {id}, data})
    + const question = await db.question.update({
    + where: {id},
    + data: {
    +... data,
    + choices: {
    + upsert: data.choices.map((choice) => ({
    + // Appears to be a prisma bug,
    + // because `|| 0` shouldn't be needed
    + where: {id: choice.id || 0},
    + create: {text: choice.text},
    + update: {text: choice.text},
    +})),
    +},
    +},
    +})
    
        return question
      },
    )
    Copy the code

    [upsert] (https://www.prisma.io/docs/reference/api-reference/prisma-client-reference#upsert) is a kind of special operations, said “if there is this project, please to upgrade it. Otherwise create it “. This is perfect for the current situation, because we don’t need the user to add three choices when creating the problem. So if the user adds another choice by editing the question, it will be created here.

    At the end

    πŸ₯³ congratulations! You created your own Blitz app! Have fun and share with your friends. Now that you’re done with this tutorial, why not try to make your voting app better? You can try:

    • Add styles (hint, tryblitz install tailwind ζˆ– blitz install chakra-ui)
    • Displays more statistical information about the vote
    • Deploy in real-time on Render or Vercel.

    If you want to share your projects with the global Blitz community, there’s no better place than Discord.

    Go to discord.blitzjs.com. Then, post the connection to the # built-in with-Blitz channel to share it with everyone!

    Conclusion the translator

    This article belongs to the first half of the blitz. js official documentation – Introduction chapter. Fourteen chapters in total (Introduction, Community, basics, pages, routing, permissions, database, Queries and Mutations, back-end architecture, deployment, recipe, configuration, CLI, and templates). In the future, I will translate the rest of the chapters irregularly and may also create some original articles.

    Blitz. Js + React Full Stack Development Manual series focuses on Blitz. Js + React Full stack development. The original translation will be updated to the Blitz. Welcome to Star, Watch or follow the public account (@ningowood).

    2021 Β© github.com/hylerrix/bl…