この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
*このエントリはシリーズ5番目の記事です。
Conect React Component を使って動的にGraphQL APIを呼び出し返って来たデータを表示する処理を書きます。 具体的にはアルバムのリストを取得し、画面に表示させます。
react-router-domモジュールを追加
npm install --save react-router-dom
App.jsにコードを追加してゆきましょう。
モジュール&コンポーネントの追加
必要なモジュールをApp.jsに追加します。
- semantic-ui-reactを追加
- react-router-domからroutingを追加
- aws-amplifyからAPI & graphqlOperationを追加
- aws-amplify-reactからConnectコンポーネントを追加
src/App.js
import React, { Component } from 'react';
import { Grid, Header, Input, List, Segment } from 'semantic-ui-react';
import {BrowserRouter as Router, Route, NavLink} from 'react-router-dom';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import { Connect, withAuthenticator } from 'aws-amplify-react';
import aws_exports from './aws-exports';
Amplify.configure(aws_exports);
データをアルファベット順にソート
取得したデータをソートする関数を追加します。
src/App.js
function makeComparator(key, order='asc') {
return (a, b) => {
if(!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) return 0;
const aVal = (typeof a[key] === 'string') ? a[key].toUpperCase() : a[key];
const bVal = (typeof b[key] === 'string') ? b[key].toUpperCase() : b[key];
let comparison = 0;
if (aVal > bVal) comparison = 1;
if (aVal < bVal) comparison = -1;
return order === 'desc' ? (comparison * -1) : comparison
};
}
クエリを変数に追加
ListAlbums、 SubscribeToNewAlbums、 GetAlbum、それぞれのクエリ変数を作成します。
src/App.js
const ListAlbums = `query ListAlbums {
listAlbums(limit: 9999) {
items {
id
name
}
}
}`;
const SubscribeToNewAlbums = `
subscription OnCreateAlbum {
onCreateAlbum {
id
name
}
}
`;
const GetAlbum = `query GetAlbum($id: ID!) {
getAlbum(id: $id) {
id
name
}
}
`;
新規アルバム作成と表示
src/App.js
class NewAlbum extends Component {
constructor(props) {
super(props);
this.state = {
albumName: ''
};
}
handleChange = (event) => {
let change = {};
change[event.target.name] = event.target.value;
this.setState(change);
}
handleSubmit = async (event) => {
event.preventDefault();
const NewAlbum = `mutation NewAlbum($name: String!) {
createAlbum(input: {name: $name}) {
id
name
}
}`;
const result = await API.graphql(graphqlOperation(NewAlbum, { name: this.state.albumName }));
console.info(`Created album with id ${result.data.createAlbum.id}`);
this.setState({ albumName: '' })
}
render() {
return (
<Segment>
<Header as='h3'>Add a new album</Header>
<Input
type='text'
placeholder='New Album Name'
icon='plus'
iconPosition='left'
action={{ content: 'Create', onClick: this.handleSubmit }}
name='albumName'
value={this.state.albumName}
onChange={this.handleChange}
/>
</Segment>
)
}
}
アルバムリストの取得と表示
src/App.js
class AlbumsList extends React.Component {
albumItems() {
return this.props.albums.sort(makeComparator('name')).map(album =>
<List.Item key={album.id}>
<NavLink to={`/albums/${album.id}`}>{album.name}</NavLink>
</List.Item>
);
}
render() {
return (
<Segment>
<Header as='h3'>My Albums</Header>
<List divided relaxed>
{this.albumItems()}
</List>
</Segment>
);
}
}
class AlbumsListLoader extends React.Component {
onNewAlbum = (prevQuery, newData) => {
// アルバムが新規に追加された時、アルバムリストを更新
let updatedQuery = Object.assign({}, prevQuery);
updatedQuery.listAlbums.items = prevQuery.listAlbums.items.concat([newData.onCreateAlbum]);
return updatedQuery;
}
render() {
return (
<Connect
query={graphqlOperation(ListAlbums)}
subscription={graphqlOperation(SubscribeToNewAlbums)}
onSubscriptionMsg={this.onNewAlbum}
>
{({ data, loading }) => {
if (loading) { return <div>Loading...</div>; }
if (!data.listAlbums) return;
return <AlbumsList albums={data.listAlbums.items} />;
}}
</Connect>
);
}
}
アルバムの詳細を取得
src/App.js
class AlbumDetailsLoader extends React.Component {
render() {
return (
<Connect query={graphqlOperation(GetAlbum, { id: this.props.id })}>
{({ data, loading }) => {
if (loading) { return <div>Loading...</div>; }
if (!data.getAlbum) return;
return <AlbumDetails album={data.getAlbum} />;
}}
</Connect>
);
}
}
class AlbumDetails extends Component {
render() {
return (
<Segment>
<Header as='h3'>{this.props.album.name}</Header>
<p>TODO: Allow photo uploads</p>
<p>TODO: Show photos for this album</p>
</Segment>
)
}
}
ホーム画面へコンポーネントを表示
src/App.js
class App extends Component {
render() {
return (
<Router>
<Grid padded>
<Grid.Column>
<Route path="/" exact component={NewAlbum}/>
<Route path="/" exact component={AlbumsListLoader}/>
<Route
path="/albums/:albumId"
render={ () => <div><NavLink to='/'>Back to Albums list</NavLink></div> }
/>
<Route
path="/albums/:albumId"
render={ props => <AlbumDetailsLoader id={props.match.params.albumId}/> }
/>
</Grid.Column>
</Grid>
</Router>
);
}
}
全部加えると以下のようなコードになります。
src/App.js
import React, { Component } from 'react';
import { Grid, Header, Input, List, Segment } from 'semantic-ui-react';
import {BrowserRouter as Router, Route, NavLink} from 'react-router-dom';
import Amplify, { API, graphqlOperation } from 'aws-amplify';
import { Connect, withAuthenticator } from 'aws-amplify-react';
import aws_exports from './aws-exports';
Amplify.configure(aws_exports);
function makeComparator(key, order='asc') {
return (a, b) => {
if(!a.hasOwnProperty(key) || !b.hasOwnProperty(key)) return 0;
const aVal = (typeof a[key] === 'string') ? a[key].toUpperCase() : a[key];
const bVal = (typeof b[key] === 'string') ? b[key].toUpperCase() : b[key];
let comparison = 0;
if (aVal > bVal) comparison = 1;
if (aVal < bVal) comparison = -1;
return order === 'desc' ? (comparison * -1) : comparison
};
}
const ListAlbums = `query ListAlbums {
listAlbums(limit: 9999) {
items {
id
name
}
}
}`;
const SubscribeToNewAlbums = `
subscription OnCreateAlbum {
onCreateAlbum {
id
name
}
}
`;
const GetAlbum = `query GetAlbum($id: ID!) {
getAlbum(id: $id) {
id
name
}
}
`;
class NewAlbum extends Component {
constructor(props) {
super(props);
this.state = {
albumName: ''
};
}
handleChange = (event) => {
let change = {};
change[event.target.name] = event.target.value;
this.setState(change);
}
handleSubmit = async (event) => {
event.preventDefault();
const NewAlbum = `mutation NewAlbum($name: String!) {
createAlbum(input: {name: $name}) {
id
name
}
}`;
const result = await API.graphql(graphqlOperation(NewAlbum, { name: this.state.albumName }));
console.info(`Created album with id ${result.data.createAlbum.id}`);
this.setState({ albumName: '' })
}
render() {
return (
<Segment>
<Header as='h3'>Add a new album</Header>
<Input
type='text'
placeholder='New Album Name'
icon='plus'
iconPosition='left'
action={{ content: 'Create', onClick: this.handleSubmit }}
name='albumName'
value={this.state.albumName}
onChange={this.handleChange}
/>
</Segment>
)
}
}
class AlbumsList extends React.Component {
albumItems() {
return this.props.albums.sort(makeComparator('name')).map(album =>
<List.Item key={album.id}>
<NavLink to={`/albums/${album.id}`}>{album.name}</NavLink>
</List.Item>
);
}
render() {
return (
<Segment>
<Header as='h3'>My Albums</Header>
<List divided relaxed>
{this.albumItems()}
</List>
</Segment>
);
}
}
class AlbumDetailsLoader extends React.Component {
render() {
return (
<Connect query={graphqlOperation(GetAlbum, { id: this.props.id })}>
{({ data, loading }) => {
if (loading) { return <div>Loading...</div>; }
if (!data.getAlbum) return;
return <AlbumDetails album={data.getAlbum} />;
}}
</Connect>
);
}
}
class AlbumDetails extends Component {
render() {
return (
<Segment>
<Header as='h3'>{this.props.album.name}</Header>
<p>TODO: Allow photo uploads</p>
<p>TODO: Show photos for this album</p>
</Segment>
)
}
}
class AlbumsListLoader extends React.Component {
onNewAlbum = (prevQuery, newData) => {
// When we get data about a new album, we need to put in into an object
// with the same shape as the original query results, but with the new data added as well
let updatedQuery = Object.assign({}, prevQuery);
updatedQuery.listAlbums.items = prevQuery.listAlbums.items.concat([newData.onCreateAlbum]);
return updatedQuery;
}
render() {
return (
<Connect
query={graphqlOperation(ListAlbums)}
subscription={graphqlOperation(SubscribeToNewAlbums)}
onSubscriptionMsg={this.onNewAlbum}
>
{({ data, loading }) => {
if (loading) { return <div>Loading...</div>; }
if (!data.listAlbums) return;
return <AlbumsList albums={data.listAlbums.items} />;
}}
</Connect>
);
}
}
class App extends Component {
render() {
return (
<Router>
<Grid padded>
<Grid.Column>
<Route path="/" exact component={NewAlbum}/>
<Route path="/" exact component={AlbumsListLoader}/>
<Route
path="/albums/:albumId"
render={ () => <div><NavLink to='/'>Back to Albums list</NavLink></div> }
/>
<Route
path="/albums/:albumId"
render={ props => <AlbumDetailsLoader id={props.match.params.albumId}/> }
/>
</Grid.Column>
</Grid>
</Router>
);
}
}
export default withAuthenticator(App, {includeGreetings: true});
Demo
アルバムを追加してみましょう。
次章ではAlbumに画像ファイルをストアできるようS3ストレージを追加します。