20210506のReactに関する記事は12件です。

Reactの状態管理比較 ~ Redux VS Apollo Client (Graph QL) ~

現場で開発しているモバイルアプリ(React Native)では、ReduxではなくApollo Clientで状態管理を行っています。 それぞれの使い方は知っているものの、正直各特長を理解した上で使い分けができているわけではありませんでした。 わからないままにしておくのはプロとして失格なので、いまさらで恥ずかしいのですが自分用に整理しました。 Redux 以下がReduxにおけるデータフローです。 ReduxではStoreで状態を一元管理することで、コンポーネント間で整合性のとれたデータを共有することができる(Single source of truth)のが特長といわれています。 しかし、実際これはローカルデータに限った話で、非同期でバックエンドのAPI呼び出しを行う場合にはReduxのMiddleware(thunk, saga)を利用する必要があります。 また、何か一つ処理を追加するたびにaction-creatorとreducerを追加しなければならないので、コードが複雑化してしまうこともあります。 コンポーネントがDBからとってきたデータを表示するまでの流れが初見では結構複雑です。 https://ncoughlin.com/posts/react-redux-asnychronous-actions-thunk/ Apollo Client 一方、以下がApollo Clientにおけるデータフローです。 コンポーネントからapollo requestを行うと、まずはresolverでキャッシュにデータがあるかを判断します。 キャッシュにデータがあればそのままpropsとしてコンポーネントに渡し、データがなければQueryまたはMutationをGraphQLサーバに対して発行します。 このようにして取得したデータは、キャッシュされた後にpropsとしてコンポーネントに渡されます。 ここまでのデータの流れをみると「ああそうなんだ〜」で終わってしまうのですが、Apollo Clientの強みは、なんといってもローカルデータをキャッシュ内部に保持し、それをAPIデータと同時に取得することができる点にあります。 ローカルデータの取得/更新については、クライアント側にSchemaとQuery/Mutationを定義することで、APIデータの取得と同様に行うことができます。 ローカルとAPIのQuery/Mutationは織り交ぜて使うことができるので、クライアント側のコードは「どこにどんなデータがあるか」を知る必要がなくなり、単純にQuery/Mutationを発行するだけとなります。 このように、Apollo ClientはReduxよりシンプルに「Single source of truth」を実現することができます。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

TSでReactの環境構築中に出た "error Cannot use JSX unless the '--jsx' flag is provided." を解消する

症状 src/app.tsx:10:17 - error TS17004: Cannot use JSX unless the '--jsx' flag is provided. 10 const element = <h1>Hello, world</h1>; Typescriptを使ってReactの環境を作っているときに発生したエラーです。 結論:下記のいずれかで治ると思います 1.使用しているtypescriptのバージョンを4.1以上にする 2.tsconfig.json で compilerOptions.jsx = react-jsx とする 1.使用しているtypescriptのバージョンを4.1以上にする 詳細は「VScodeでTSを使うと出るCannot use JSX unless the '--jsx' flag is provided. ts(17004)を解決する。」などをご参照ください。 なお、自分の場合はこれでは直らなかった。というより、エラー発生時点で既に4.1以上を使っていた。そんなときは下記2を試してみましょう。 2.tsconfig.json で compilerOptions.jsx = react-jsx とする tsconfig.json { "compilerOptions": { "jsx": "react-jsx", } } 参考:"Error: Cannot use JSX unless the '--jsx' flag is provided" when using "extends" 自分の場合はこれで治った。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React Slickの基本的な使い方

スライダー&カルーセルを実装するモジュールでjQueryプラグインで広く知られるslickのReact版。 React Slick 導入方法 インストール npm install react-slick --save or yarn add react-slick cssの設置 設置場所はcreat-react-appで作成している時のpublic/index.html相当のheadタグ内に以下を記述する。 ・CDN <link rel="stylesheet" type="text/css" charset="UTF-8" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick.min.css" /> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.6.0/slick-theme.min.css" /> ・インストールする場合はインストール後にimportする npm install slick-carousel --save or yarn add slick-carousel ↓ import "~slick-carousel/slick/slick.css"; import "~slick-carousel/slick/slick-theme.css"; オプション オプション名 type default 説明 accessibility bool true tabキー、矢印キーによる操作を可能にするかどうか adaptiveHeight bool false スライドの高さを自動で調整するかどうか afterChange func default スライド移動後のコールバック関数 appendDots func default ドット部分にカスタムテンプレートを適用できる、customPagingと同じ働き arrows bool true スライドの左右にある矢印の表示・非表示 asNavFor ref undefined 別のスライダーとの同期を可能にする autoplaySpeed int 3000 自動でスライドするときのスピード autoplay bool false 自動でスライドするかどうか beforeChange func null スライド移動前のコールバック関数 centerMode bool false 表示しているスライドを真ん中にもってくるか(複数枚表示時) centerPadding string 50px スライドとスライドの間隔 className string "" スライダー内のdivに任意のクラス名を付与できる customPaging func ドット部分にカスタムテンプレートを適用できる dotsClass string 'slick-dots' dotに任意のクラス名を付与できる dots bool default ドットの表示・非表示 draggable bool true デスクトップ上でのドラッグによるスクロールの有効にするか cssEase(easing) string 'linear アニメーションのイーシングの変更 fade bool default スライドの切り替えをfadeにするか focusOnSelect bool false 選択したスライドまで動くか(複数枚表示) infinite bool true スライダーを無限にスライドできるか initialSlide int 0 初期表示のスライドを指定できる lazyLoad ondemand/progressive null 画像やコンポーネントを遅延ロードする onEdge func null onInit func null componentWillMountにあたるもの onLazyLoad func null 遅延ロード後のコールバック関数 onReInit func null componentDidUpdateにあたるもの onSwipe func null スワイプによるスライド変更後のコールバック関数 pauseOnDotsHover bool bool ドットにホバーしているときに自動スライドを止めるか pauseOnFocus bool false スライドを選択したときに自動スライドを止めるか pauseOnHover bool true スライドにホバーしているときに自動スライドを止めるか responsive array null 任意のブレイクポイントで何枚スライドを表示するかなどの指定ができる rows integer 1 行数の指定 rtl bool false スライドの順番を逆にする slide string 'div' スライドの要素を変更できる slidesPerRow int 1 グリッドモード時に表示するスライドの数(rowsを指定したときのみ有効) slidesToScroll int 1 一度のスクロールで何枚スライドするか slidesToShow int 1 一度に表示するスライドの数 speed int 500 スライドするスピード swipeToSlide bool false ドラッグ、スワイプでのスクロールを有効にするか swipe bool true スワイプでのスライドの変更の有効/無効 touchMove bool true ドラッグやスワイプでスライダーが動くか touchThreshold int 5 スライドを変更する際のドラッグ、スワイプの値の指定(数字を大きくすればすこしのドラッグ、スワイプで次のスライドにいく) useCSS bool true CSS Transitionsの有効/無効 useTransform bool true CSS Transformの有効/無効 variableWidth bool false スライドごとのwidthの指定の有効/無効 vertical bool false 縦向きスライドの有効/無効 ※onEdgeのみ公式の説明や検索しても使用方法がわかりませんでした... ※公式ではeasingとなっていますが、githubのissueを確認したところ正しくはcssEaseのようです。 メソッド メソッド名 args default 説明 slickGoTo index, dontAnimate null, false 引数に現在のスライドの順番を取れる,dotAnimateをtrueにするとアニメーションがなくなる slickNext none none 次スライドにいく slickPause none none autoplayを停止する slickPlay none none autoplayを有効にする slickPrev none none 前のスライドにいく サンプル 簡単な実用例 app.js import Slider from "react-slick"; function App() { const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 1, slidesToScroll: 1, arrow: true, }; const items = [ {title: "name1", img: 'http://placehold.it/500x200'}, {title: "name2", img: 'http://placehold.it/500x200'}, {title: "name3", img: 'http://placehold.it/500x200'}, {title: "name4", img: 'http://placehold.it/500x200'}, {title: "name5", img: 'http://placehold.it/500x200'}, {title: "name6", img: 'http://placehold.it/500x200'}, ] return ( <div className="App"> <p>simple slider</p> <div className="container"> <Slider {...settings}> {items && items.map(item => { return ( <div> <img src={item.img} /> <p>{item.title}</p> </div> ) })} </Slider> </div> </div> ); } export default App; 簡単な説明 一行目でSliderをインポート。これで該当要素をラップすることでスライダーとして機能します。 定数のsettingsにはオブジェクト型でオプションを記述し、Sliderに渡してあげます。 また業務ではapiなどを使用しデータを取得後に、表示させるようなことが多いと思ったので、ダミーデータは連想配列の形にしてみました。 1点注意なのはitems &&の部分が無いと初期表示がバグってしまう事象があったので、データを取得してきて表示するような場合は忘れないように記述するといいと思います。 またcssですが、上記にあるオプションを使用して、ドット部分などに任意のクラス名をつけてcssを適用してもいいですし、デフォルトのクラスのままでもcssは反映されます。 もっとシンプルな例や上記のオプションやメソッドを使用した公式サンプルを見てみてもいいと思います。 https://react-slick.neostack.com/docs/example/simple-slider 補足 公式sampleにあるAsNavForですが3つ以上のスライダーを同期させたい場合は以下のようにするとできました。 constructor(props) { super(props); this.state = { nav1: null, nav2: null, nav3: null, }; } componentDidMount() { this.setState({ nav1: this.slider1, nav2: this.slider2, nav3: this.slider3, }); } <Slider className="background-slider" asNavFor={this.state.nav2} ref={slider => (this.slider1 = slider)} {...settings1}> ... </Slider> <Slider className="content-slider" asNavFor={this.state.nav3} ref={slider => (this.slider2 = slider)} {...settings2}> ... </Slider> <Slider className="person-slider" asNavFor={this.state.nav1} ref={slider => (this.slider3 = slider)} {...settings3}> ... </Slider>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React&CSSで文字を上下左右の中央揃え

./src/App.js import logo from "./logo.svg"; import "./App.css"; function App() { return ( <div className="App"> <h1>Hello React!</h1> </div> ); } export default App; ./src/App.css .App { display: flex; align-items: center; justify-content: center; background-color: #e8d3d1; height: 100vh; }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Reactで起動時ローディングを実装

./src/App.js import logo from "./logo.svg"; import "./App.css"; import { useState, useEffect } from "react"; import Loader from "react-loader-spinner"; function App() { const [loading, setLoading] = useState(true); useEffect(() => { setTimeout(() => { setLoading(false); }, 2000); }, []); return ( <div className="App"> <header className="App-header"> {loading ? ( <Loader type="Puff" color="#007bbb" height={128} width={128} timeout={3000} /> ) : ( <h1>Hello React!</h1> )} </header> </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【webpack v5】React+webpackでHello Worldを表示するまで

結構前に「React+webpackでHello Worldを表示するまで」という記事を書いたのですが、あれからだいぶ環境が変わったので、改めて投稿します。 もし「ここおかしいよ!」って部分があればご指摘いただけるとありがたいです。 環境 Node.js 15.0.1 npm 7.6.0 下準備 ディレクトリを作成し、npm initするところまでは一緒です。 mkdir project-name cd project-name npm init インストール 必要なものをインストールします。 以前はwebpack-dev-serverを入れていましたが、webpack v5だとまだ対応していないようで、動かなかったため、今回は入れません。 代わりにwebpack-cli serveを使用します。 #webpack npm install --save-dev webpack webpack-cli #babel npm install --save-dev @babel/core @babel/cli @babel/preset-env @babel/preset-react babel-loader #react npm install --save-dev react react-dom TypeScript使いたいのでこれもインストールします。 npm install --save-dev typescript ts-loader npm install --save-dev @babel/preset-typescript @types/react @types/react-dom スタイルシート関連です。 mini-css-extract-pluginはCSSのみ別ファイルに書き出すために必要。 npm install --save-dev css-loader sass-loader style-loader node-sass npm install --save-dev mini-css-extract-plugin webpackの設定 webpack.config.js を作成し、以下を記述します。 コンポーネントなどのファイルを保存するディレクトリが「src」、ローカルサーバー用のディレクトリが「public」、ビルドしたファイルの保存先が「dist」です。 以前はbabelの設定を別ファイルにしていましたが、こちらにまとめています。 modeは適宜書き換えてください。 watchオプション入れていますが、こちらも特に必要ない場合はfalseで。 webpack.config.js const MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { mode: 'development', devtool: 'source-map', entry: { app: './src/index.tsx' }, output: { filename: 'js/[name].bundle.js', path: `${__dirname}/dist` }, module: { rules: [ { test: /\.ts[x]?$/, exclude: /node_modules/, use: 'ts-loader' }, { test: /\.(sa|sc|c)ss$/, exclude: /node_modules/, use: [ MiniCssExtractPlugin.loader, { loader: 'css-loader', options: { url: false } }, 'sass-loader' ] } ] }, resolve: { extensions: ['.ts', '.tsx', '.js', '.json'] }, target: ['web', 'es6'], plugins: [ new MiniCssExtractPlugin({ filename: 'css/app.css' }) ], watch: true, watchOptions: { ignored: /node_modules/ }, devServer: { open: true, contentBase: `${__dirname}/public` } }; package.jsonの設定 コマンドでwebpackのどの機能を実行するかを設定します。 npm startでローカルサーバー起動、npm run buildでビルド出来るようになります。 --mode developmentの部分は適宜書き換えてください。 package.json { "name": "project-name", "version": "1.0.0", "description": "", "main": "index.js", "scripts": { // ここを編集 "build": "webpack", "start": "webpack-cli serve --mode development", "test": "echo \"Error: no test specified\" && exit 1" }, ... } TypeScriptの設定 TypeScriptのための設定ファイルを用意します。 正直オプション多すぎてよくわかっていません... tsconfig.json { "compilerOptions": { "sourceMap": true, "target": "es6", "module": "es2015", "jsx": "react", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "lib": [ "es2020", "dom" ], "allowSyntheticDefaultImports": true, "esModuleInterop": true }, "include": [ "**/*.ts", "**/*.tsx" ], "exclude": [ "node_modules" ] } Hello Worldを表示 表示用のテンプレートを作成します。 JS、CSSは自動で読み込まれないので、webpackで設定した名前のファイルを読み込むよう記述します。 public/index.html <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <!-- ビルドするCSSファイル --> <link rel="stylesheet" href="css/app.css"> <!-- 追加で読み込みたい場合は、public内に保存して読み込む --> <link rel="stylesheet" href="css/plugins.css"> </head> <body> <div id="root"></div> <!-- ビルドするJSファイル --> <script src="js/app.bundle.js"></script> </body> </html> reactで文字を表示。 src/index.tsx import React from "react" import ReactDOM from "react-dom" ReactDOM.render( <h1>Hello World!</h1>, document.getElementById("root") ) npm startでサーバーを起動して、http://localhost:8080 にアクセスします。 「Hello World!」と表示されれば完了です。 最終的な構成 src/components内にコンポーネントを保存して、index.tsxでインポートします。 ├─ dist // ビルドしたファイルの保存先 ├─ node-modules ├─ public // ローカルサーバー用のファイル │ ├─ index.html ├─ src │ ├─ components // 作成したコンポーネントを保存 │ └─ index.tsx ├─ package.json ├─ package-lock.json ├─ tsconfig.json └─ webpack.config.js おまけ ファイルを分割する よく1つのファイルにまとめて書き出そうとして「重いよ!」って怒られるので、分割します。 ビルドすると、app.bundle.jsとplugins.bundle.jsが作成されます。 webpack.config.js module.exports = { ... entry: { app: './src/index.tsx', plugins: './src/plugins.tsx' }, output: { filename: 'js/[name].bundle.js', path: `${__dirname}/dist` }, ... 参考 参考にさせていただきました。ありがとうございます。 tsconfig.jsonの全オプションを理解する(随時追加中) webpack.config.js の書き方をしっかり理解しよう webpack v5 npx webpack-dev-server で dev server が動かないにハマる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がファビコン作ってみた

ファビコンとは favicon(ファビコン)は「Favourite icon(お気に入りのアイコン)」の略で、アドレスバーやブラウザのタブ、スマートフォンやタブレットなどでWebページを"ホーム画面"に置いた際に表示されるアイコンなどを指します。 主なサイズ 16px × 16px:IE タブ 32px × 32px:Chrome、Firefox、Safari などのタブ 64px × 64px:Windowsデスクトップアイコン 180px × 180px:iPhone&iPad など。 今回は初めてなので、とりあえず 32px × 32pxだけで作成しました。 この辺りは余裕が出てきたら色んなサイズにも対応していきたいです。 デザイン デザインアプリやウェブブラウザでまずはデザインを作成する。 私は自分のリングネームにちなんでこのようなデザインを作成してみました。 SHUだけにShoe(片方の靴)。。。 空調の音は響き渡りましたでしょうか? 気をつける点 デザインを作成する上で気をつける点としては大きく以下の2点が挙げられます。 アイコンはとても小さいのでシンプルなデザインを心がける事 統一感を持たせるために、アイコンのカラーとアプリのテーマカラーを揃える事 フォーマットの変換 今回はこちらのサイトを利用して、サイズとフォーマットを変更しました。 ファビコン favicon.icoを作ろう! サイト内で拡張子をicoに変更し、出来上がったファイルをpublicフォルダ直下に置きました。 32pxだと分かりやすくする為に、ファイル名をfavicon32.icoとし、 headダグの中で以下の記述をします。 index.html <link rel="icon" href="%PUBLIC_URL%/favicon32.ico" /> 結果 これを使って ↓ こう! ひとまず初めてファビコンを作成から設定まで出来ましたが、他にももっと良いやり方があればぜひ教えてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がファビコンを作ってみた

ファビコンとは favicon(ファビコン)は「Favourite icon(お気に入りのアイコン)」の略で、アドレスバーやブラウザのタブ、スマートフォンやタブレットなどでWebページを"ホーム画面"に置いた際に表示されるアイコンなどを指します。 主なサイズ 16px × 16px:IE タブ 32px × 32px:Chrome、Firefox、Safari などのタブ 64px × 64px:Windowsデスクトップアイコン 180px × 180px:iPhone&iPad など。 今回は初めてなので、とりあえず 32px × 32pxだけで作成しました。 この辺りは余裕が出てきたら色んなサイズにも対応していきたいです。 デザイン デザインアプリやウェブブラウザでまずはデザインを作成する。 私は自分のリングネームにちなんでこのようなデザインを作成してみました。 SHUだけにShoe(片方の靴)。。。 空調の音は響き渡りましたでしょうか? 気をつける点 デザインを作成する上で気をつける点としては大きく以下の2点が挙げられます。 アイコンはとても小さいのでシンプルなデザインを心がける事 統一感を持たせるために、アイコンのカラーとアプリのテーマカラーを揃える事 フォーマットの変換 今回はこちらのサイトを利用して、サイズとフォーマットを変更しました。 ファビコン favicon.icoを作ろう! サイト内で拡張子をicoに変更し、出来上がったファイルをpublicフォルダ直下に置きました。 32pxだと分かりやすくする為に、ファイル名をfavicon32.icoとし、 headダグの中で以下の記述をします。 index.html <link rel="icon" href="%PUBLIC_URL%/favicon32.ico" /> 結果 これを使って ↓ こう! ひとまず初めてファビコンを作成から設定まで出来ましたが、他にももっと良いやり方があればぜひ教えてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【MaterialUI】TextFieldでラベルと文字が重なってしまう状態の対処法【React】

症状 MaterialUIのTextFieldに初期値を設定して、実装。実装した画面でブラウザをリロードすると、以下のようにラベルと初期値が重なってしまう事象が発生しました。 フォーカスすると、ラベルが上に動き正常な状態になりますが、フォーカスを外すとこの状態に戻ってしまいます。 以下が今回使ったMaterialUIの部品のコードになります。 MaterialUITextField import React, { Fragment,useState,useEffect } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; const useStyles = makeStyles((theme) => ({ root: { '& .MuiTextField-root': { margin: theme.spacing(1), width: '25ch', }, }, })); export function react:MaterialUITextField(prop) { const classes = useStyles(); const [value, setValue] = useState(prop.name); const handleChange = (event) => { setValue(event.target.value); }; return ( <wrapper> <Fragment> <form className={classes.root} noValidate autoComplete="off"> <div> <TextField id="outlined-multiline-static" label="名前" multiline rows={4} defaultValue={value} variant="outlined" onChange={handleChange} /> </div> </form> </Fragment> } </wrapper> ); } 同様のエラーが他の方でも発生しているようで、valueの中身がnull,undefindの時に事象が発生するとのこと。 console.logでvalue値を確認したところ、初回レンダリングのときにはundefindになっていることを確認。 また、ログと事象を観察すると、2回目以降で正しく値が取得でき初期値の更新ができても、初期値が入るのみでラベルが被る事象は解消されないようでした。 そのことから、初回レンダリング時のうまく処理をしなければいけないということが分かりました。 そう考えて以下のコードを追加しましたが、上手く機能しませんでした。 materialUITextField useEffect(()=>{ setValue("") },[]) また、ほかの方が解決した方法も試しましたが、解決できませんでした。 defaultValue={value===undefined? "":value} 解決方法 useStateを使い、初期値であるvalueに値が設定されるまでtextfieldを読み込ませないようにすることで、エラーを回避できました。 以下が解決時のコードになります。 valueの値がundefindやnullなどの未設定状態のときはLOADINGの文字が表示されるようにし、valueの値にporpの値が設定されたときにTextFiledを表示させるように制御しました。 MaterialUITextField import React, { Fragment,useState,useEffect } from 'react'; import { makeStyles } from '@material-ui/core/styles'; import TextField from '@material-ui/core/TextField'; const useStyles = makeStyles((theme) => ({ root: { '& .MuiTextField-root': { margin: theme.spacing(1), width: '25ch', }, }, })); export function MaterialUITextField(prop) { const classes = useStyles(); const Fooddescription = food.description const [value, setValue] = useState(prop.name); const handleChange = (event) => { setValue(event.target.value); }; //propの値が変更された時に処理される useEffect(() => { setValue(prop.name); }, [prop]); return ( <wrapper> { value === undefined || value === null? //ここでvalue値の判定をしている //valueが未設定の場合の処理 <Fragment> LOADING </Fragment> :    //valueが設定済みになった時の処理 <Fragment> <form className={classes.root} noValidate autoComplete="off"> <div> <TextField id="outlined-multiline-static" label="名前" multiline rows={4} defaultValue={value===undefined? "":value} //念のためここでも制御するが、たぶんいらない variant="outlined" onChange={handleChange} /> </div> </form> </Fragment> } </wrapper> ); } 参考 MaterialUI Text Field (テキストフィールド) https://material-ui.com/ja/components/text-fields/ javascript : React MaterialUIラベルがテキストと重なっています https://www.fixes.pub/program/282923.html react,material-uiのselectにて、labelと要素が重なって表示される https://ja.stackoverflow.com/questions/54793/react-material-ui%E3%81%AEselect%E3%81%AB%E3%81%A6-label%E3%81%A8%E8%A6%81%E7%B4%A0%E3%81%8C%E9%87%8D%E3%81%AA%E3%81%A3%E3%81%A6%E8%A1%A8%E7%A4%BA%E3%81%95%E3%82%8C%E3%82%8B
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでGridを試してみる

./src/components/ui/MyGrid.js import React from "react"; import { makeStyles, Grid, Paper } from "@material-ui/core"; const useStyles = makeStyles((theme) => ({ paper: { backgroundColor: "#1e50a2", color: "#f8f4e6", height: "128px", }, })); const MyGrid = () => { const classes = useStyles(); return ( <Grid container spacing={1}> <Grid item xs={12}> <Paper className={classes.paper}>xs=12</Paper> </Grid> <Grid item xs={6}> <Paper className={classes.paper}>xs=6</Paper> </Grid> <Grid item xs={6}> <Paper className={classes.paper}>xs=6</Paper> </Grid> <Grid item xs={4}> <Paper className={classes.paper}>xs=4</Paper> </Grid> <Grid item xs={4}> <Paper className={classes.paper}>xs=4</Paper> </Grid> <Grid item xs={4}> <Paper className={classes.paper}>xs=4</Paper> </Grid> <Grid item xs={3}> <Paper className={classes.paper}>xs=3</Paper> </Grid> <Grid item xs={3}> <Paper className={classes.paper}>xs=3</Paper> </Grid> <Grid item xs={3}> <Paper className={classes.paper}>xs=3</Paper> </Grid> <Grid item xs={3}> <Paper className={classes.paper}>xs=3</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> <Grid item xs={2}> <Paper className={classes.paper}>xs=2</Paper> </Grid> </Grid> ); }; export default MyGrid; ./src/App.js import logo from "./logo.svg"; import "./App.css"; import MyGrid from "./components/ui/MyGrid"; function App() { return ( <div className="App"> <MyGrid /> </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

React/Material-UIでfooterを実装

./src/components/ui/Footer.js import React from "react"; import { makeStyles } from "@material-ui/core"; const useStyles = makeStyles({ footer: { color: "#ebf6f7", backgroundColor: "#0f2350", width: "100%", position: "absolute", bottom: 0, }, }); const Footer = () => { const classes = useStyles(); return <div className={classes.footer}>Footer</div>; }; export default Footer; ./src/App.js import logo from "./logo.svg"; import "./App.css"; import Footer from "./components/ui/Footer"; function App() { return ( <div className="App"> <Footer /> </div> ); } export default App;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ポケモンAPIを使ってGraphQLを今更覚える!

目的 有志の方が制作されているGraphQL のポケモン APIを使って ポケモン検索アプリを作る過程で React + TypeScript + GraphQL を覚える! 前述 本記事で作ったアプリは GitHub で公開しているので、よかったら記事を一緒に確認してください 事前準備 とりあえず React + TypeScript 環境を作る npx create-react-app pokemon-graphql-practice --template typescript たったのこれだけで React + TypeScript 環境が作成できる(神) cd pokemon-graphql-practice yarn start 不要なファイルは削除しましょう src/App.css src/App.text.tsx src/logo.svg src/setupTest.ts App.tsx も不要な記述を消してとりあえずスッキリさせます function App() { return <div>pokemon-graphql-practice</div> } src/App.css graphql-codegen で型定義を作る GraphQL の Schema 情報から型定義ファイルを作成してくれるすごいやつです 公式 Docsの Installation を参考にインストールする yarn add graphql yarn add -D @graphql-codegen/cli yarn add -D @graphql-codegen/schema-ast // schema.graphqlを自動で作ってくれる! セッティングを行う yarn graphql-codegen init 色々聞かれるけどとりあえず全部 Enter ですっ飛ばして OK です script 名を聞かれるのでそこだけgenerateと入力します graphql-codegen を実行するのにyarn generateで実行できるようになります codegen.yml が作成されるので、以下のように修正します schema: "https://graphql-pokemon2.vercel.app" # GraphiQLを指定 generates: ./src/@types/types.d.ts: plugins: - "typescript" - "typescript-operations" # fqueryやmutationで使用するOperationとかの型定義まで作ってくれる ./schema.graphql: # これ以下を記述するとschema.graphqlをDocumentから作ってくれる(スゴイ) plugins: - schema-ast ここまで来たらあとは実行するのみです! yarn generate 以下のファイルが作成されます ./src/@types/types.d.ts ./schema.graphql Apollo をインストール Apolloはフロントエンドから簡単に GraphQL を操作できるようにするライブラリです yarn add @apollo/client 実装 ここまで来たらもう準備万端! 早速実装に入りましょう!!! ...と言いたいところですが、その前に graphql-pokemon が提供している API 仕様を確認しましょう Pikachu の番号/名前/画像 url を取得する Query を試す GraphiQLを開き、右上の Docs を開きます(Docs の見方は省略します) query:Query をクリックすると、query と pokemons と pokemon の Fields があることがわかります 今回 Pikachu のデータを取得したいので、pokemon を見てみると、pokemon(id: String,name: String): Pokemonとありますね String 型の id または name を引数として渡すことで、Pokemon 型のレスポンスを返却することがわかります では Pokemon 型の詳細を見てみましょう Pokemon 型が持つ Field が表示されました いろんなデータがありますがとりあえず今回必要なのは number name image ですね これを元に左側のエディタで Query を作成してみましょう! serachPikachu Query を作りました これを実行すると pikachu のデータが取れることがわかります この"data"から始まる JSON が、クライアント側で Query を実行した時のレスポンスとして返されます GraphiQL 超便利!!! さてこれを実際にアプリで実行してみましょう! Apollo の準備 src/graphql/client.ts を作成し、そこに Apollo を使うためのクライアントを用意してあげます ApolloClient を初期化します src/graphql/client.ts import { ApolloClient, InMemoryCache } from "@apollo/client" const apolloClient = new ApolloClient({ uri: "https://graphql-pokemon2.vercel.app/", cache: new InMemoryCache(), }) export { apolloClient } uri には GraphiQL を、cache には InMemoryCache のインスタンスを渡します cache を設定することで同じクエリが発行された場合、自動的にキャッシュから結果を返却してくれるので、パフォーマンスがよくなるみたいです とりあえず入れておいて損は無いかと思います App.ts に ApolloProvider と先ほど初期化した AppoloClient をインポートして、ApolloProvider でアプリ全体を囲ってあげます import { ApolloProvider } from "@apollo/client" import { apolloClient } from "./graphql/client" function App() { return ( <ApolloProvider client={apolloClient}> // 追加 <div>pokemon-graphql-practice</div> </ApolloProvider> // 追加 ) } これでこの ApolloProvider に囲われてるコンポーネント内で、GraphQL の query や mutation を実行することができるようになります 結果を表示しよう GraphQL API を実行して結果を表示するコンポーネントを作っていきます src/graphql/queries.ts に先ほど GraphQL で確認した Query を記載しておきましょう gql にテンプレートリテラルで Query を書くといい感じにリクエストを作ってくれるみたいです src/graphql/queries.ts import gql from "graphql-tag" export const searchPikachu = gql` query searchPikachu { pokemon(name: "pikachu") { number name image } } ` src/components/SearchResultField.tsx を作って、Query を実行し結果を表示するコンポーネントを作ります src/components/SearchResultField.tsx import { useQuery } from "@apollo/client" import { Query } from "../@types/types" import { searchPikachu } from "../graphql/queries" const SearchResultField = () => { const { loading, error, data } = useQuery<Query>(searchPikachu) if (loading) return <>"Loading..."</> if (error) return <>`Error! ${error.message}`</> return ( <div> <div>No: {data?.pokemon?.number}</div> <div>Name: {data?.pokemon?.name}</div> {data?.pokemon?.image ? ( <img src={data?.pokemon?.image} alt={data?.pokemon?.name ?? ""} /> ) : ( <div>no image.</div> )} </div> ) } export { SearchResultField } useQuery にジェネリクスとして、data の型を渡すことができます @types から Query をとってきて渡すことで、ちゃんと data が Query 型だと VSCode が判定してくれました 当初の想定では data は Pokemon 型になると思っていましたが、data は GraphiQL での結果の JSON (以下)のように pokemon キーが一番上にあったので、data は Query 型にしました { "data": { "pokemon": { ... } } } 簡単に説明していきます useQuery import { useQuery } from "@apollo/client" const { loading, error, data } = useQuery<Query>(searchPikachu) Apollo が用意してくれている Hooks です 通信の状況に応じて loading,error,data の値が変わるのでとても便利 ハンドリング if (loading) return <>Loading...</> if (error) return <>Error! {error.message}</> loading が true の場合はローディング中の文字、エラー発生時にはメッセージを返却するようにしています <></>これはフラグメントと行ってとりあえず囲みたい時に使います とても便利 データの表示 テストなので特別なハンドリングとかはせず、?.でごり押してます img だけ src と alt に undifind を渡せなかったので undifind の場合 no image. が表示されるようにしました return ( <div> <div>No: {data?.pokemon?.number}</div> <div>Name: {data?.pokemon?.name}</div> {data?.pokemon?.image ? ( <img src={data?.pokemon?.image} alt={data?.pokemon?.name ?? ""} /> ) : ( <div>no image.</div> )} </div> ) ここまででPikachuのデータが表示されるようになりました 動的にポケモンの名前が動的に設定できるようにしよう ここまでで結果を取得、表示ができました あとは Pikachu 以外のポケモンも調べられるようにしたいので、検索フォームを作って動的にポケモンを検索できるようにします まずは GraphiQL で変数を使った Query を作成してみます 変数を使った Query Query に変数を渡すには以下のように記載します -2a9914945eb4.png) 左下の QUERY VARIABLES で変数に Charizard を入れて、それを左上の query で使用しています 検索結果が 006 Charizard になったのがわかりますね src/graphql/queries.ts に追加しておきましょう src/graphql/queries.ts export const searchPokemon = gql` query searchPokemon($name: String) { pokemon(name: $name) { number name image } } ` 検索フォームを作成 src/components/SearchForm.tsx を作成し、検索フォームを作っていきます src/components/SearchForm.tsx import { FormEvent, useRef } from "react" type PropType = { setpokemonName: React.Dispatch<React.SetStateAction<string>> } const SearchForm: React.FC<PropType> = ({ setpokemonName }) => { const ref = useRef<HTMLInputElement>(null) const submitHandler = (e: FormEvent) => { e.preventDefault() if (ref !== null && ref.current !== null) { setpokemonName(ref.current.value) } } return ( <form onSubmit={submitHandler}> <input type="text" ref={ref} placeholder="input Pokemon's name" /> <input type="submit" value="Search"></input> </form> ) } export { SearchForm } src/App.tsx もこれに伴い修正 src/App.tsx function App() { const [pokemonName, setpokemonName] = useState("") return ( <> // 追加 <SearchForm setpokemonName={setpokemonName}></ SearchForm> // 変更 <ApolloProvider client={apolloClient}> <SearchResultField></SearchResultField> </ApolloProvider> </> // 追加 ) } React の解説記事では無いので詳細な説明は省きますが、 input:text を useRef Hooks で定義 form の onSubmit で ref を参照し、value を取得している App.tsx で useState で作成した pokemonName のセッター?を渡して、onSubmit でそこに value を突っ込む って感じのフォームにしています Submit ボタンを押した時、テキストボックスの値が App.tsx の pokemonName に、入るようになりました 検索条件を動的な値に設定する さて、検索フォームから受け取った pokemonName を使って検索結果を表示しましょう src/components/SearchResultField.tsx // ↓追加 type PropType = { pokemonName: string } const SearchResultField: React.FC<PropType> = ({ pokemonName }) => { // 変更 const { loading, error, data } = useQuery<Query> (searchPokemon, { variables: { name: pokemonName } }) // 変更 if (!pokemonName) return <></> // 追加 if (loading) return <>Loading...</> if (error) return <>Error! {error.message}</> if (!data || !data.pokemon) return <>No Data.</> // 追加 // ↓変更 return ( <> <div>No: {data.pokemon.number}</div> <div>Name: {data.pokemon.name}</div> {data.pokemon.image ? <img src={data.pokemon.image} alt={data.pokemon.name ?? ""} /> : <div>no image.</div>} </> ) } useQuery<Query> (searchPokemon, { variables: { name: pokemonName } }) useQuery に searchPokemon を第一引数で渡し、第二引数に variables を保持した Object を渡しています この variables には先ほど GraphiQL の左下での QUERY VARIABLES で確認した物と同じ型のオブジェクトを渡します useQuery は変数として渡したパラメータが変更された時に再度 Query を実行してくれるので、これで検索条件を可変にすることができました 最後に App.tsx から pokemonName を渡します ついでに pokemonName が存在しない場合は何も表示しないようにしました App.tsx <ApolloProvider client={apolloClient}> {pokemonName && <SearchResultField pokemonName={pokemonName}> // 変更</SearchResultField>} </ApolloProvider> これで Submit が押される度に検索結果が更新されるようになりました! まとめ React + TypeScript + GraphQL でポケモンの画像検索ができる簡易的なアプリを作成しました 大好きなポケモンでGraphQLを勉強出来て、graphql-pokemonの作者には感謝してもしきれません が、欲を言うなら全ポケモンに対応して欲しい・・・あと日本語・・・ GraphQL の勉強のついでに作ってみましたが、GraphiQL 最高すぎるし、graphql-code-generator で型定義ファイルが作れるの天才だし、Apollo がとても使いやすくて、簡単でした GraphiQL のお陰で REST API より簡単にお試しできるので、とてもとっつきやすかったです ただ GraphQL じゃないと出来ないことっていうのがそこまで内容に見受けられました 最近流行りの gRPC とかだと REST ではできないことが出来たりするみたいです とても気に入ったので GraphQL には是非これから流行って欲しいですが、gRPC も勉強して流れに乗り遅れないようにしないといけないなと思いました・・・ 今回は query しか使っていませんが(graphql-pokemon には query しか用意されていません) GitHub API とかには mutation もあるので mutation の記事もそのうち書きたいです バックエンドの GraphQL の書き方も勉強しないと・・・ 参考 graphql-pokemon graphql-pokemon の GraphiQL graphql-code-generator 公式 Docs GraphQL Code Generator の使い方。〜GraphQL の Schema から TS の型定義を自動生成する〜 Apollo Docs
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む