- 投稿日:2021-01-17T20:38:59+09:00
[React] Reactの学習をします(1-2)コンポーネントの分割
Reactの学習をします(1-2)
Reactの学習をしてみることにしました。
前回の記事 : [React] Reactの学習をします(1-1)教材
likr さんが公開している「Reactチュートリアル1:犬画像ギャラリーを作ろう」という記事を教材に学習させて頂きます。
素晴らしい教材をありがとうございます。
学習日記
本日は「Web アプリをマークアップする」~「コンポーネントの分割」までやりました!
Web アプリをマークアップする
※ 教材から箇条書き的に抜粋させて頂きます。
src/App.js
・React では、JSX という拡張構文で、 JavaScript の中に HTML のマークアップを記述します
・React では、JSX を返すような関数を特別に コンポーネント と呼びます。
・コンポーネントは、アプリケーションの状態から JSX 式を組み立てて、その結果を HTML としてレンダリングします。
CSS フレームワークを使う
※ 教材から箇条書き的に抜粋させて頂きます。
・本稿では、CSS フレームワークの一種である Bulma を使用します。
Bulma公式ページ : Bulma公式
コンポーネントの分割
※ 教材から箇条書き的に抜粋させて頂きます。
・Appコンポーネントの中身が随分長くなってしまいました。こういうときはコンポーネントの分割をしましょう。
・コンポーネントを使う側のコンポーネントを 親コンポーネント、別のコンポーネントから使われる側のコンポーネントを 子コンポーネント と呼びます。
下図のように表示させることができました!
- 投稿日:2021-01-17T17:17:39+09:00
【初心者】#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.jsimport React, {useState, useEffect} from 'react'; import axios from 'axios';
const [post, setPosts] = useState([])
オブジェクト配列で取得してくるので初期値は配列を入れていきます。JSONはアクセスしてデータを確認してみてください。
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.jsimport 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.jsimport 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そして、渡されたデータを受け取って、タイトルと、本文を表示に使っています。
文字数によって、カードの高さずれてますね…
高さ指定、一定文字数以上は消すためにCSS追加します。makeStyleの書き換え
Material-UIでCSSを書いてみます。慣れるまでなかなか扱いにくそうですね・・・
material-react/src/components/BodyCard.jsconst 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ですが、-
をなくして、キャメルケースにする必要があるのでご注意ください。次回は
次回は、react-router-domを導入してルーティングを設定します。
画像の「詳細をみる」から詳細ページを表示する機能を追加します。
- 投稿日:2021-01-17T17:00:32+09:00
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 buildreactアプリの作成
docker-compose run --rm node sh -c "npm install -g create-react-app && create-react-app reactapp"reactアプリの起動
docker-compose uplocalhost:3000でアクセスできれば成功!
自分用メモ
コンテナに入る
docker exec -it コンテナ名 bash
react-routerをインストール
npm install -S react-router-dom
- 投稿日:2021-01-17T15:31:32+09:00
React Bootstrapのカレンダーを使う
概要
Reactのカレンダー用ライブラリといえば、react-calendarやreact-datepickerといったものが挙げられます。
これらのライブラリは機能も多いし便利なのですが、React Bootstrapを使っている場合は他のフォーム項目と同様の実装にしたいという思いがあって、React BootstrapでBootstrapのカレンダー用コンポーネントを使えないのかと考え少し調べてみました。対応
React Bootstrap Datepicker Exampleの記事にある通り、react-bootstrapのFormコンポーネントを使って
type="date"
と設定するだけです。react-calendarのように細かい制御まではできないと思いますが、Bootstrapを使っていてとりあえずカレンダーを表示させるだけで十分であれば、非常に簡単な実装で済ませられます。表示イメージ
下記の通りテキストボックスとカレンダーボタンが表示されて、カレンダーボタンを押すとカレンダーが表示されます。
テキストボックスでも日付は入力できるのですが、中途半端な入力でsubmitをすると自動でチェックをかけてくれます。
- 投稿日:2021-01-17T12:51:30+09:00
【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
- 投稿日:2021-01-17T11:10:27+09:00
Reactで作るタブ
Reactで作る理解のしやすいタブのテンプレがQiitaに置いてあればと思い、書きました。
説明はなしでコードを載せます。こちらの記事を見て書きましたので詳しくという方は是非ご参照ください!環境
- react 17.0.1
- next 10.0.5
- typescript 4.1.3
完成はこんな感じ
構成説明
- index.tsx (タブを表示するとこ)
- Tabs.tsx
- TabTitle.tsx
- Tab.tsx
Tabs.tsx
Tabs.tsximport 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.tsximport 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.tsximport React from 'react'; type Props = { title: string; }; const Tab: React.FC<Props> = ({ children }) => { return <div>{children}</div>; }; export default Tab;index.tsx
index.tsximport 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;
- 投稿日:2021-01-17T03:19:08+09:00
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/
- 投稿日:2021-01-17T00:21:34+09:00
next.js heroku デプロイ Error: Couldn't find that app.
next.jsをherokuにデプロイする日本語記事が見当たらなかったため投稿しました。
環境:
node.js
react
next.jsherokuへ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 startheroku触ったことなかったのでビクビクしてたけど、ngrokみたいな感じかな
恐らく、localhostとurlを繋いでるけどそのルート記述が間違ってたのかな。。理解したら追記します!
- 投稿日:2021-01-17T00:20:31+09:00
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 に実際にうごく様子を公開しています。