アプリ開発を楽しむ【#7:Reduxで状態管理2】
前回までの記事 アプリ開発を楽しむ【#1:アプリの概要】 アプリ開発を楽しむ【#2:環境構築1(React+TypeScript)】 アプリ開発を楽しむ【#3:環境構築2 (ESLint+Prettier)】 アプリ開発を楽しむ【#4:ヘッダー】 アプリ開発を楽しむ【#5:MyTogoList】 アプリ開発を楽しむ【#6:Reduxで状態管理1】
今回は、MyTogoListの状態をユーザーの操作によって更新していきたいと思います。
具体的には、以下の2つのことをしていきます。 ①MyTogoListのチェックボックスにチェックをつけたり、外したりできるようにする。 ②削除アイコンをクリックすると、該当のtogoが削除される。
現状では、MyTogoListのチェックボックスをクリックしても、削除アイコンをクリックしても、何も起こりません。 これをクリックしたときに、ステート(状態)を変更して、画面に反映していきます。
1.チェックボックスの状態更新
MyTogoListにあるチェックボックスは、行ったらチェックする用のチェックボックスです。 togoのデータ項目は次のようになっていました。
要素 | 説明 |
---|---|
id | 識別子 |
done | 行ったらチェック(True or False) |
location | 行きたい場所(行った場所) |
position | 緯度経度(lat: 緯度、lng: 経度) |
tag | タグ |
チェックボックスをクリックしたときに、doneの項目をfalseもしくはtrueに変更するようにしていきます。
frontend/app/src/redux/togoSlice.tsを次のように修正します。
export const togoSlice = createSlice({
name: 'togo',
initialState,
reducers: {
getTogoList(state: TogoState, action: PayloadAction<Togo[]>) {
state.togoList = action.payload;
},
// ここから追加
updateTogoDone(state: TogoState, action: PayloadAction<number>) {
state.togoList = state.togoList.map((togo) => {
if (togo.id === action.payload) {
togo.done = !togo.done;
}
return togo;
});
},
// ここまで追加
},
});
// ここから修正
export const { getTogoList, updateTogoDone } = togoSlice.actions;
// ここまで修正
updateTogoDoneというreducerとactionをつくっています。
updateTogoDone(state: TogoState, action: PayloadAction<number>) {
state.togoList = state.togoList.map((togo) => {
if (togo.id === action.payload) {
togo.done = !togo.done;
}
return togo;
});
},
状態を変更したいtogoのidをpayloadとして渡し、そのpayloadとtogoリストのidが同じだった場合に、doneの状態を変更しています。
frontend/app/src/components/MyTogoList.tsxを修正します。
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Checkbox,
Chip,
Fab,
Link,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Typography,
} from '@mui/material';
import {
Add as AddIcon,
Delete as DeleteIcon,
LocationOn as LocationOnIcon,
ModeEditOutline as ModeEditOutlineIcon,
} from '@mui/icons-material';
import { RootState, AppDispatch } from '../../redux/store';
// ここから修正
import { getTogoList, updateTogoDone, initialState } from '../../redux/togoSlice';
// ここまで修正
import sampleTogoList from '../../sampleData/togo';
const MyTogoList = () => {
const dispatch: AppDispatch = useDispatch();
const { togoList } = useSelector((state: RootState) => state.togo || initialState);
useEffect(() => {
dispatch(getTogoList(sampleTogoList));
}, [dispatch]);
// ここから追加
const handleChangeTogoDone = (id: number) => {
dispatch(updateTogoDone(id));
};
// ここまで追加
return (
<>
<Typography
component="h2"
variant="h6"
color="primary"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
My List
</Typography>
<Table size="small">
<TableHead>
<TableRow>
<TableCell> </TableCell>
<TableCell>場所</TableCell>
<TableCell>タグ</TableCell>
<TableCell>地図</TableCell>
<TableCell>編集</TableCell>
<TableCell>削除</TableCell>
</TableRow>
</TableHead>
<TableBody>
{togoList &&
togoList.map((item) => (
<TableRow key={item.id}>
<TableCell>
// ここから修正
<Checkbox checked={item.done} onChange={() => handleChangeTogoDone(item.id)} />
// ここまで修正
</TableCell>
<TableCell>{item.location}</TableCell>
<TableCell>
<Chip label={item.tag} color="primary" size="small" />
</TableCell>
<TableCell>
<Link
href={`https://maps.google.co.jp/maps?ll=${item.position.lat},${item.position.lng}&z=20`}
underline="none"
target="_blank"
rel="noopener"
>
<LocationOnIcon />
</Link>
</TableCell>
<TableCell>
<ModeEditOutlineIcon sx={{ cursor: 'pointer' }} />
</TableCell>
<TableCell>
<DeleteIcon sx={{ cursor: 'pointer' }} onClick={() => handleDelteTogo(item.id)} />
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Fab
color="primary"
aria-label="add"
size="medium"
sx={{ position: 'absolute', top: 16, right: 16 }}
>
<AddIcon />
</Fab>
</>
);
};
export default MyTogoList;
これで、チェックボックスにチェックをつけたり、外したりすることができるようになりました。
2.削除時の状態更新
削除する場合も同様です。 frontend/app/src/redux/togoSlice.tsを次のように修正します。
export const togoSlice = createSlice({
name: 'togo',
initialState,
reducers: {
getTogoList(state: TogoState, action: PayloadAction<Togo[]>) {
state.togoList = action.payload;
},
updateTogoDone(state: TogoState, action: PayloadAction<number>) {
state.togoList = state.togoList.map((togo) => {
if (togo.id === action.payload) {
togo.done = !togo.done;
}
return togo;
});
},
// ここから追加
deleteTogo(state: TogoState, action: PayloadAction<number>) {
state.togoList = state.togoList.filter((togo) => togo.id !== action.payload);
},
// ここまで修正
},
});
// ここから修正
export const { getTogoList, updateTogoDone, deleteTogo } = togoSlice.actions;
// ここまで修正
状態を変更したい(ここでは削除したい)togoのidをpayloadとして渡し、そのpayloadとtogoリストのidが同じものは、togoListの配列から除外しています。
frontend/app/src/components/MyTogoList.tsxを修正します。
import { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
Checkbox,
Chip,
Fab,
Link,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
Typography,
} from '@mui/material';
import {
Add as AddIcon,
Delete as DeleteIcon,
LocationOn as LocationOnIcon,
ModeEditOutline as ModeEditOutlineIcon,
} from '@mui/icons-material';
import { RootState, AppDispatch } from '../../redux/store';
// ここから修正
import { getTogoList, updateTogoDone, deleteTogo, initialState } from '../../redux/togoSlice';
// ここまで修正
import sampleTogoList from '../../sampleData/togo';
const MyTogoList = () => {
const dispatch: AppDispatch = useDispatch();
const { togoList } = useSelector((state: RootState) => state.togo || initialState);
useEffect(() => {
dispatch(getTogoList(sampleTogoList));
}, [dispatch]);
const handleChangeTogoDone = (id: number) => {
dispatch(updateTogoDone(id));
};
// ここから追加
const handleDeleteTogo = (id: number) => {
dispatch(deleteTogo(id));
};
// ここまで追加
return (
<>
<Typography
component="h2"
variant="h6"
color="primary"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
My List
</Typography>
<Table size="small">
<TableHead>
<TableRow>
<TableCell> </TableCell>
<TableCell>場所</TableCell>
<TableCell>タグ</TableCell>
<TableCell>地図</TableCell>
<TableCell>編集</TableCell>
<TableCell>削除</TableCell>
</TableRow>
</TableHead>
<TableBody>
{togoList &&
togoList.map((item) => (
<TableRow key={item.id}>
<TableCell>
<Checkbox checked={item.done} onChange={() => handleChangeTogoDone(item.id)} />
</TableCell>
<TableCell>{item.location}</TableCell>
<TableCell>
<Chip label={item.tag} color="primary" size="small" />
</TableCell>
<TableCell>
<Link
href={`https://maps.google.co.jp/maps?ll=${item.position.lat},${item.position.lng}&z=20`}
underline="none"
target="_blank"
rel="noopener"
>
<LocationOnIcon />
</Link>
</TableCell>
<TableCell>
<ModeEditOutlineIcon sx={{ cursor: 'pointer' }} />
</TableCell>
<TableCell>
// ここから修正
<DeleteIcon
sx={{ cursor: 'pointer' }}
onClick={() => handleDeleteTogo(item.id)}
/>
// ここまで修正
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
<Fab
color="primary"
aria-label="add"
size="medium"
sx={{ position: 'absolute', top: 16, right: 16 }}
>
<AddIcon />
</Fab>
</>
);
};
export default MyTogoList;
これで、削除のアイコンをクリックすると、該当のtogoが削除されるようになります。
実際には、Storeの状態を変更しているだけなので、ブラウザの更新ボタンを押すと、再度sampleTogoListが読み込まれ、最初の状態に戻ります。
実際のアプリケーションでは、ステート(状態)の更新を行う前に、データベースなどの元データを更新する必要があります。 例えば、削除した場合は、データベースと通信して、そのデータをデータベースから削除し、Storeの状態を更新する流れになると思います。
今回はここまでです。
コードはGitHubに置いてありますのでよければ参考にしてください。 mainブランチは常に最新のものになります。 今回の内容はblog_7のブランチを参照してください。 https://github.com/KINE-M/togo_app