20210117のReactに関する記事は9件です。

[React] Reactの学習をします(1-2)コンポーネントの分割

Reactの学習をします(1-2)

Reactの学習をしてみることにしました。
前回の記事 : [React] Reactの学習をします(1-1)

教材

Reactチュートリアル1:犬画像ギャラリーを作ろう

likr さんが公開している「Reactチュートリアル1:犬画像ギャラリーを作ろう」という記事を教材に学習させて頂きます。

素晴らしい教材をありがとうございます。

学習日記

本日は「Web アプリをマークアップする」~「コンポーネントの分割」までやりました!

Web アプリをマークアップする

※ 教材から箇条書き的に抜粋させて頂きます。

src/App.js

・React では、JSX という拡張構文で、 JavaScript の中に HTML のマークアップを記述します

・React では、JSX を返すような関数を特別に コンポーネント と呼びます。

・コンポーネントは、アプリケーションの状態から JSX 式を組み立てて、その結果を HTML としてレンダリングします。

CSS フレームワークを使う

※ 教材から箇条書き的に抜粋させて頂きます。

・本稿では、CSS フレームワークの一種である Bulma を使用します。

Bulma公式ページ : Bulma公式

コンポーネントの分割

※ 教材から箇条書き的に抜粋させて頂きます。

・Appコンポーネントの中身が随分長くなってしまいました。こういうときはコンポーネントの分割をしましょう。

・コンポーネントを使う側のコンポーネントを 親コンポーネント、別のコンポーネントから使われる側のコンポーネントを 子コンポーネント と呼びます。

下図のように表示させることができました!

023a.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】#2 React axiosでAPI データ取得

APIからJSONデータを取得して表示する

取得前回はMaterial-UIで見た目を整えましたが、
データをハードコードしただけなので、APIからデータを取得してみたいと思います。
useStateを使います。

今後、Python DjangoでAPIサーバー作ってAPIサーバーを作ってみたいと思いますが、最初はJSON Placeholderというサービスで取得したいと思います。

コードは前回の引き続きになります。

シリーズ

使用環境

  • axios 0.21.1
  • react 17.0.1
  • material-ui 4.11.2

axiosインストールして使う

$ npm install axios

画面を読み込んだ時に一度だけ、APIでデータを取得するようにします。
まず、必要なものをインポートして

material-react/src/components/Content.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';

const [post, setPosts] = useState([])
オブジェクト配列で取得してくるので初期値は配列を入れていきます。

JSONはアクセスしてデータを確認してみてください。

https://jsonplaceholder.typicode.com/posts

GETで取得するとこのようなデータです。

{
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  }

のようなデータが取得できます。

useStateで一回だけ最初にAPI叩いてデータ取得

データは

useEffect(() => {
        axios.get('https://jsonplaceholder.typicode.com/posts')
        .then(res => {
            setPosts(res.data)
        })
    }, [])

のようにすればGETで取得して、
const [post, setPosts] = useState([])
のようにしたので、postに格納されます。

※ 注意ですが、最後の, []がないと何度も何度もデータを取得することになるので注意!APIリクエスト回数で課金されるサービスで、これで作ってしまったら大変です

この空の配列は、ページ開いたら一度だけ。引数をあげなければ、何か処理起こったら毎回動きます。
条件を指定して、処理を実行することもできます。

結果的にこんな感じで処理を書きました。

material-react/src/components/Content.js
import React, {useState, useEffect} from 'react';
import axios from 'axios';

import { Grid } from '@material-ui/core'
import BodyCard from './BodyCard'

const cardContent = 
    {
        avatarUrl: "https://joeschmoe.io/api/v1/random",
        imageUrl: "https://picsum.photos/150"
    }

function Content() {
    const [post, setPosts] = useState([])

    useEffect(() => {
        axios.get('https://jsonplaceholder.typicode.com/posts')
        .then(res => {
            setPosts(res.data)
        })
    }, [])
    const getCardContent = getObj => {
        const bodyCardContent = {...getObj, ...cardContent};
        return (
            <Grid item xs={12} sm={4} key={getObj.id}>
                <BodyCard {...bodyCardContent} />
            </Grid>
        );
    };
    return (
        <Grid container spacing={2}>
            {post.map(contentObj => getCardContent(contentObj))}
        </Grid>
    )
}

export default Content

アバターとイメージは前回のままです。
APIで取得したデータをスプレッド演算子
const bodyCardContent = {...getObj, ...cardContent};で結合させて、渡しているだけです。

material-react/src/components/BodyCard.js
import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import Card from '@material-ui/core/Card';
import CardActions from '@material-ui/core/CardActions';
import CardContent from '@material-ui/core/CardContent';
import Button from '@material-ui/core/Button';
import Typography from '@material-ui/core/Typography';
import CardHeader from '@material-ui/core/CardHeader';
import Avatar from '@material-ui/core/Avatar';
import IconButton from '@material-ui/core/IconButton';
import StarBorderOutlinedIcon from '@material-ui/icons/StarBorderOutlined';
import { CardMedia } from '@material-ui/core';

const useStyles = makeStyles({
    bullet: {
      display: 'inline-block',
      margin: '0 2px',
      transform: 'scale(0.8)',
    },
    title: {
      fontSize: 14,
    },
    pos: {
      marginBottom: 12,
    },
});


function BodyCard(props) {
    const { userId, id, title, body, avatarUrl, imageUrl } = props;
    const classes = useStyles();
    const bull = <span className={classes.bullet}></span>;
    return (
        <Card variant="outlined">
            <CardHeader
                avatar={<Avatar src={avatarUrl} />}
                action={
                <IconButton aria-label="settings">
                    <StarBorderOutlinedIcon />
                </IconButton>
                }
                title={title}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent>
            <Typography variant="body2" component="p">
                {body}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard

そして、渡されたデータを受け取って、タイトルと、本文を表示に使っています。

スクリーンショット 2021-01-15 19.28.45.png

文字数によって、カードの高さずれてますね…
高さ指定、一定文字数以上は消すためにCSS追加します。

makeStyleの書き換え

Material-UIでCSSを書いてみます。慣れるまでなかなか扱いにくそうですね・・・

material-react/src/components/BodyCard.js
const useStyles = makeStyles({
.
.
.
    cHeader: {
        height: '50px',
        overflow: 'hidden',
        textOverflow: 'ellipsis',
        whiteSpace: 'nowrap',
        "& .MuiCardHeader-content": {
            overflow: 'hidden'
        }
    },
    cContent: {
        height: '200px',
        overflow: 'hidden'
    }
});


function BodyCard(props) {
    const { userId, id, title, body, avatarUrl, imageUrl } = props;
    const classes = useStyles();
    const bull = <span className={classes.bullet}></span>;
    return (
        <Card variant="outlined">
            <CardHeader
                avatar={<Avatar src={avatarUrl} />}
                action={
                <IconButton aria-label="settings">
                    <StarBorderOutlinedIcon />
                </IconButton>
                }
                className={classes.cHeader}
                title={title}
            />
            <CardMedia style={{ height: "150px" }} image={imageUrl} />
            <CardContent className={classes.cContent}>
            <Typography variant="body2" component="p">
                {body}
            </Typography>
            </CardContent>
            <CardActions>
            <Button size="small">詳細をみる</Button>
            </CardActions>
        </Card>
    );
}

export default BodyCard

苦戦しながら、やったらこんな感じでうまくいきました。
"& .MuiCardHeader-content"は生成されたクラスで、検証ツールで調べて指定してます。&はSCSS的と同じ感じですね。知らないと、ハマりそう。

あと、textOverflowはcssだとtext-over-flowですが、-をなくして、キャメルケースにする必要があるのでご注意ください。

スクリーンショット 2021-01-15 20.41.19.png

次回は

次回は、react-router-domを導入してルーティングを設定します。
画像の「詳細をみる」から詳細ページを表示する機能を追加します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DockerでReactの環境構築

・参考
https://engineer-ninaritai.com/docker-react/

できること
Dockerを使ってReactのページを開く

Dockerfile

FROM node:14.13.1 
WORKDIR /usr/src/app/

docker-compose.yml

version: '3'
services:
  node:
    build:
      context: .
    tty: true
    environment:
      - NODE_ENV=development
    volumes:
    - ./:/usr/src/app
    command: sh -c "cd reactapp && npm start"
    ports:
    - "3000:3000"

docker-compose.ymlがあるディレクトリでdockerコマンドを実行

docker-compose build

reactアプリの作成

docker-compose run --rm node sh -c "npm install -g create-react-app && create-react-app reactapp"

reactアプリの起動

docker-compose up

localhost:3000でアクセスできれば成功!

自分用メモ
コンテナに入る
docker exec -it コンテナ名 bash
react-routerをインストール
npm install -S react-router-dom

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Bootstrapのカレンダーを使う

概要

Reactのカレンダー用ライブラリといえば、react-calendarreact-datepickerといったものが挙げられます。
これらのライブラリは機能も多いし便利なのですが、React Bootstrapを使っている場合は他のフォーム項目と同様の実装にしたいという思いがあって、React BootstrapでBootstrapのカレンダー用コンポーネントを使えないのかと考え少し調べてみました。

対応

React Bootstrap Datepicker Exampleの記事にある通り、react-bootstrapのFormコンポーネントを使ってtype="date"と設定するだけです。react-calendarのように細かい制御まではできないと思いますが、Bootstrapを使っていてとりあえずカレンダーを表示させるだけで十分であれば、非常に簡単な実装で済ませられます。

表示イメージ

下記の通りテキストボックスとカレンダーボタンが表示されて、カレンダーボタンを押すとカレンダーが表示されます。

テキストボックスでも日付は入力できるのですが、中途半端な入力でsubmitをすると自動でチェックをかけてくれます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Typescript + React】e.target.files[0]のObject is possibly 'null'.エラー

修正前

「Object is possibly 'null'.」のTypescriptエラーが発生

const SampleFunc: FC = () => {
  const onUploadImage = React.useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      const file = e.target.files[0]; // エラー発生箇所
      // fileの処理
    },
    [],
  );

  return (
    <>
      <input type="file" onChange={onUploadImage} />
    </>
  );
};

export default SampleFunc;

修正内容

変更  e.target -> e.currentTarget
追記  if (e.currentTarget.files !== null) {/* 処理 */}

修正後

const SampleFunc: FC = () => {
  const onUploadImage = React.useCallback(
    (e: React.ChangeEvent<HTMLInputElement>) => {
      if (e.currentTarget.files !== null) {
        const file = e.currentTarget.files[0];
        // fileの処理
      }
    },
    [],
  );

  return (
    <>
      <input type="file" onChange={onUploadImage} />
    </>
  );
};

export default SampleFunc;

追記

e.targetとe.currentTargetは違う場所を指しているらしい
https://qiita.com/sitilma/items/f26f5d16e455a0c68071

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで作るタブ

Reactで作る理解のしやすいタブのテンプレがQiitaに置いてあればと思い、書きました。
説明はなしでコードを載せます。こちらの記事を見て書きましたので詳しくという方は是非ご参照ください!

環境

  • react 17.0.1
  • next 10.0.5
  • typescript 4.1.3

完成はこんな感じ

image.png
image.png
image.png

構成説明

  • index.tsx (タブを表示するとこ)
  • Tabs.tsx
  • TabTitle.tsx
  • Tab.tsx

Tabs.tsx

Tabs.tsx
import React, { ReactElement, useState } from 'react';
import TabTitle from './TabTitle';

type Props = {
  children: ReactElement[];
};

const Tabs: React.FC<Props> = ({ children }) => {
  const [selectedTab, setSelectedTab] = useState(0);

  return (
    <div>
      <ul>
        {children.map((item, index) => (
          <TabTitle
            key={index}
            title={item.props.title}
            index={index}
            setSelectedTab={setSelectedTab}
          />
        ))}
      </ul>
      {children[selectedTab]}
    </div>
  );
};

export default Tabs;

TabTitle.tsx

TabTitle.tsx
import React, { useCallback } from 'react';

type Props = {
  title: string;
  index: number;
  setSelectedTab: (index: number) => void;
};

const TabTitle: React.FC<Props> = ({ title, index, setSelectedTab }) => {
  const onClick = useCallback(() => {
    setSelectedTab(index);
  }, [setSelectedTab, index]);

  return (
    <li>
      <button onClick={onClick}>{title}</button>
    </li>
  );
};

export default TabTitle;

Tab.tsx

Tab.tsx
import React from 'react';

type Props = {
  title: string;
};

const Tab: React.FC<Props> = ({ children }) => {
  return <div>{children}</div>;
};

export default Tab;

index.tsx

index.tsx
import React from 'react';
import Tab from '../components/Tabs/Tab';
import Tabs from '../components/Tabs/Tabs';

const index: React.FC = () => {
  return (
    <Tabs>
      <Tab title='Lemon'>Lemon is yellow</Tab>
      <Tab title='Strawberry'>Strawberry is red</Tab>
      <Tab title='Pear'>Pear is green</Tab>
    </Tabs>
  );
};

export default index;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Material-ui インストールコマンド

Material-uiのインストールコマンド

下記コマンドを実行

npm install --save @material-ui/icons @material-ui/core @material-ui/system @material-ui/styles

意味

@matesial-ui/core→Material-uiを入手する
@material-ui/icons→公式マテリアルアイコンを使用できる
@material-ui/system→"style functions" と呼ばれる低レベルのユーティリティ関数を提供し、強力な設計システムを構築する
@material-ui/styles→Material-UIコンポーネントを使用していないReactアプリケーションのスタイルを設定する

公式ドキュメントはこちら
https://material-ui.com/ja/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

next.js heroku デプロイ Error: Couldn't find that app.

next.jsをherokuにデプロイする日本語記事が見当たらなかったため投稿しました。

環境:
node.js
react
next.js

herokuへpushまでは省略します。
push後urlへアクセスすると拒絶されたところから
ログを確認してから出直せと言われたので、

heroku logs -tail
Error: Couldn't find that app.
 ›
 ›   Error ID: not_found

で、結論から
https://github.com/mars/heroku-nextjs
を参考にしました。

package.json
{
  "name": "next-yaninavi",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "next",
    "build": "next build",
    "start": "next start -p $PORT"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^10.0.5",
    "react": "^17.0.1",
    "react-dom": "^17.0.1"
  }
}
npm run build
NODE_EV=production npm run start

heroku触ったことなかったのでビクビクしてたけど、ngrokみたいな感じかな
恐らく、localhostとurlを繋いでるけどそのルート記述が間違ってたのかな。。

理解したら追記します!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Hooks ~ State Hookの使い方 ~

React を触っていて学んだことをまとめていきます。

React Hooks とは

これを用いることで、クラスコンポーネントでこれまで利用されてきたReactの機能を関数コンポーネントでも利用できるようになります。

React は長年書かれるうちに、シンプルに簡潔に書くこと、テストの容易性から、関数コンポーネントで書くことが主流になった。
しかし、当時の関数コンポーネントは React のライフサイクルメソッドなどを利用することができず、クラスコンポーネントの利用や併用、ライブラリの利用で解決していた。

React v16.8 でReact Hooks が導入された。
React Hooks がもたらした恩恵は

  • コンポーネントがシンプルになる
  • 関数として、コンポーネントからロジックを抽出、使い回しが用意になる。
  • class の this に悩まされなくなる

State Hook とは

コンポーネントに状態(State)を持たせたいときに利用する。
State は設定したコンポーネント内でのみ呼び出しができる。もし他コンポーネントにpropsとして渡してあげれば他コンポーネントからの表示、更新などが可能ではある。

State Hook の使い方

シンプルに書くならこんな感じで利用できる。

// useState を import する。
import { useState } from 'react'

const  CountButton = () => {
   // State の変数、更新のためのメソッド名を定義する。
   // useState(0) で State の初期値を設定できる。
   const [count, setCount] = useState(0)
   return (
    <div>
      {// 変数名で呼び出しができる。
      }
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  )
}

TypeScript との併用

TypeSctipt との併用も、問題なく利用ができます。

const [count, setCount] = useState<number>(0)

子コンポーネントから、親コンポーネントの状態を更新する。

子コンポーネントに、propsとして、状態の値、コールバック関数を用いることで可能です.
こうすることで、他のコンポーネントでも再利用が可能な子コンポーネントの作成ができそうです。

App.tsx(親コンポーネント)
import React, { useState, useCallback } from "react"
import { InputForm } from "./InputForm"

export default function App() {
  const [ content, setContent ] = useState<string>("test")

  const onChangeContent = useCallback(
    (event: React.ChangeEvent<HTMLInputElement>) => {
      setContent(event.target.value)
    },
    [],
  )

  return (
    <div className="App">
      <InputForm content={content} onChange={onChangeContent} />
      <span>{content}</span>
    </div>
  )
}
InputForm.tsx(子コンポーネント)
import React from "react"

interface Props {
  content: string;
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
}

export const InputForm = (props: Props) => {
  return <input defaultValue={props.content} onChange={props.onChange}></input>
}

codesandbox に実際にうごく様子を公開しています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む