KAEDE Hack blog

JavaScript 中心に ライブラリなどの使い方を解説する技術ブログ。

Next.js で ビジネス定型文生成アプリを作り直す

why

  • Next.conf stage X で "全て Next.js で作れ" ってあった
  • その気になった、結局あれ React で作ってたからな
  • 設計が汚かったので綺麗にコンポーネント作りたくなった
  • ts はやめとく

upgrade to latest

nextjs.org

npm install -g next@latest react@latest react-dom@latest

+ react@17.0.1
+ react-dom@17.0.1
+ next@10.0.0
added 814 packages from 440 contributors in 60.126s
  • react@17, next@10, に更新

start

nextjs.org

npx create-next-app biz-text-generator                                       
✔ Pick a template › Default starter app                                                           
Creating a new Next.js app in /Users/kaede/code/biz-text-generator.                               
                                                                                                  
Installing react, react-dom, and next using yarn...      

Success! Created biz-text-generator at /Users/kaede/code/biz-text-generator
  • なぜ yarn?
next dev
Port 3000 is already in use.
  • blitz のが残ってるのか...
  • next では3000 使ってるから3001 って割り振ってくれない
  • VScode の統合ターミナルで動いてたのを切った
next dev
ready - started server on http://localhost:3000

Internal Server Error

  • えええ...
  • yarn じゃないとダメなの?
Success! Created biz-text-generator at /Users/kaede/code/biz-text-generator                       
Inside that directory, you can run several commands:                                              
                                                                                                  
  yarn dev                                                                                        
    Starts the development server.                                                                
                                                                                                  
  yarn build                                                                                      
    Builds the app for production.                                                                
                                                                                                  
  yarn start                                                                                      
    Runs the built app in production mode.                                                        
                                                                                                  
We suggest that you begin by typing:                                                              
                                                                                                  
  cd biz-text-generator                                                                           
  yarn dev   
  • って出てたしなぁ
yarn dev
yarn run v1.22.5
$ next dev
ready - started server on http://localhost:3000
event - compiled successfully

f:id:kei_s_lifehack:20201028042714p:plain

  • yarn ならいけたぜ!
  • next dev が走ってるらしいけど、まぁいいや
 import Head from 'next/head'

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>biz-text-gen</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <h1>ビジネス文章ジェネレーター</h1>
    </div>
  )
}
  • pages/index.js を変更

f:id:kei_s_lifehack:20201028043204p:plain

  • 読み込みを確認
  • GitHub に push もした

Layout 適用コンポーネントの作成

layout.module.css

  • モジュールの CSS をかく

  • components/ を作成

  • その中に layout.module.css を作成
.container {
  max-width: 36rem;
  padding: 0 1rem;
  margin: 3rem auto 6rem;
}

layout.js

  • 今作った モジュールの CSS を適用するための wrapper CSS をかく
  • components/layout.js
import styles from './layout.module.css'

export default function layout ({children}) {
  return <div className={styles.container}>{children}</div>
}
  • 同階層から css を持ってきて、引数の children に style.container を適用するだけのjs

Layout.js の index への適用

  • index.js には
import Head from 'next/head'

export default function Home() {
  return (
    <div className="container">
      <Head>
        <title>biz-text-gen</title>
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <h1>ビジネス文章ジェネレーター</h1>
    </div>
  )
}
  • が書いてあるこれに
import Layout from '../components/layout'
  • で Layout を持ってきて
      <Layout>
          <h1>ビジネス文章ジェネレーター</h1>
      </Layout>
  • これで Layout の module css が適用された h1 になった

f:id:kei_s_lifehack:20201030040959p:plain

  • ご覧の通り

MUI Paper で綺麗な 大枠を作りたかった

  • 全体を MUI のコンテナと Paper に入れてしまおうと思う
  • layout.js に MUI のタグ入れる
  • layout.css を前回の css と同じにする
.container {
  display: grid;
  height: 100%;
  place-items: center center;
}
  • 前回の container クラスの css に変更
  • ちょっと上が空いた

tech.playground.style

そもそもの話ですが、Next.jsのデフォルト設定でMaterial UIを使えない理由は、Material UIがサーバーサイドレンダリングSSR)に対応できないからです。

ということで、Material UIをSSRに対応させる設定が必要です。

github.com

  • 公式から

  • pages/app.js, pages/document.js

とこれらが使用している

  • src/theme.js

を paste しないといけないらしい。

コードを肥大化させたくないので MUI Paper は諦める?

やはり MUI Paper 使いたい

  • 考えたらさっきのはSSR の話で、私は SSR しないだろ
npm install @material-ui/core

+ @material-ui/core@4.11.0

コンテナと紙を見てみる

import Container from '@material-ui/core/Container';
import Paper from '@material-ui/core/Paper';
  • Layout.js で import
export default function layout ({children}) {
  return (
      <div className={styles.container}>
          <Container>
              <Paper>
                  {children}
              </Paper>
          </Container>
      </div>
  )
}
  • Layout.js で children を包む

f:id:kei_s_lifehack:20201031182013p:plain

f:id:kei_s_lifehack:20201031182143p:plain

  • カードに入っている。
  • これでとりあえずコンテナの style は分離できそう。多少綺麗なコードにできた

f:id:kei_s_lifehack:20201031182223p:plain

  • 次はこうやって、
    • 相手の組織と名前
    • 自分の組織と名前
  • を入るようにする

ユーザーの 入力エリアと文章の返答エリアの作成

kei-s-lifehack.hatenablog.com

  • これを参考にする
  • next js blog みたいに 使う

汚い state たち

  const [theirName, setTheirName] = useState('人事 太郎');
  const [myName, setMyName] = useState('面接 やる太郎');
  const [theirOrg, setTheirOrg] = useState('GAFA 株式会社');
  const [myOrg, setMyOrg] = useState('すごい大学');
  const [mtgDateOne, setMtgDateOne] = useState('2020-10-01T10:30');
  const [mtgDateTwo, setMtgDateTwo] = useState('2020-10-02T10:30');
  const [mtgDateThree, setMtgDateThree] = useState('2020-10-03T10:30');
  • こんなコードをかいていた

stackoverflow.com

        const [state, setState] = useState({ fName: "", lName: "" });
        const handleChange = e => {
        const { name, value } = e.target;
        setState(prevState => ({
            ...prevState,
            [name]: value
        }));
        };

        <input
            value={state.fName}
            type="text"
            onChange={handleChange}
            name="fName"
        />
        <input
            value={state.lName}
            type="text"
            onChange={handleChange}
            name="lName"
        />
  • これで短くできそう

NameForm を index.js で読み込む

import NameForm from '../components/NameForm'

      <Layout>
          <h1>ビジネス文章ジェネレーター</h1>
          <NameForm/>
      </Layout>
  • NameForm を読み込むようにする

state の自動生成コードを使う

    const [state, setState] = useState({ fName: "", lName: "" });
    const handleChange = e => {
        const { name, value } = e.target;
        setState(prevState => ({
            ...prevState,
            [name]: value
        }));
    };
  • NameForm にこれを 書いて
  return (
      <div>
            <input
                value={state.fName}
                type="text"
                onChange={handleChange}
                name="fName"
            />
            <input
                value={state.lName}
                type="text"
                onChange={handleChange}
                name="lName"
            />
            <h2>{state.fName}, {state.lName}, </h2>
      </div>
  )
  • これで読み込めた

f:id:kei_s_lifehack:20201031191451p:plain

  • これで簡単に state を増やしまくれる

Paper の二重読み込み

  • MUI Paper を重ねる
            <Paper elevation={5}>
                <h2>{state.hisCompany}, {state.hisName}, 様</h2>
            </Paper>

f:id:kei_s_lifehack:20201031192902p:plain

  • elevatoin 5 で重なったけど、マージンを効かせなければいけない

stackoverflow.com

<Box m={2} pt={3}>
  <Button color="default">
    Your Text
  </Button>
</Box>
  • このようにwrapper が必要

f:id:kei_s_lifehack:20201031202743p:plain

  • free area を作ってみた
  • 何がしたいのかわからなくなった...
 const hisCompanyInput = () => {
        return (
            <input
                value={state.hisCompany}
                type="text"
                onChange={handleChange}
                name="hisCompany"
            />
        )
    }
  • input の定義
const output = () => {
        const firstGreeding = `お世話になっております`
        const finalGreeding = `以上、よろしくお願いいたします`
        return (
            <Box m={2}>
                <Paper elevation={5}>
                    <h2>{state.hisCompany}, {state.hisName}, 様</h2>
                    <h2>{state.myCompany}, {state.myName}, です</h2>
                    <h2>{firstGreeding}</h2>
                    
                    <h2>{state.freeArea}</h2>

                    <h2>{finalGreeding}</h2>
                </Paper>
            </Box>
        )
    }
  • out put に input した state と定型文を並べる
  return (
      <div>
            {hisCompanyInput()}
            {hisNameInput()}
            {myCompanyInput()}
            {myNameInput()}
            <br/>
            {freeAreaInput()}
            {output()}
      </div>
  • return で input と output を並べる
  • copy btn 実装するのに改行記号処理がわからん

deploy to vercel

biz-text-generator.vercel.app

React + gh-pages より断然早いな

今回学んだこと

  • mui paper は elevation 増やせば重ねられる
  • mui の 部品は Box で wrap して margin や padding を適用する
  • 全体の中央揃えなどは layout.js layout.modules.css を噛ませる
  • state を宣言しまくりたい時は楽に書く方法がある
  • return で返す html は 変数に入れて分割しまくったほうが、行数は長くなるけど見通しがいい