画面をリロードしてもStateの内容が消えないように、ローカルストレージを利用して簡易メモアプリを作ってみました。
Contents
環境の準備
実行環境
- VS code:v1.62.3
- Node.js:v14.17.3
- React:v17.0.2
プロジェクトの作成
React Create Appで作成します。
React Create Appで作成されたソースの変更
- 15行目でbootstrapをCDNで読み込んでいます。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<title>React App</title>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css" crossorigin="anonymous" >
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
- 18行目のMemoPageを読み込むだけです。
import './App.css';
import { MemoPage } from './memo_comp';
const App = () => {
return (
<div>
<h1 className='bg-primary text-white display-4'>React</h1>
<div className='container'>
<h4 className='my-3'>Memo.</h4>
<MemoPage />
</div>
</div>
)
}
export default App
簡易メモアプリの作成
ローカルストレージにStateを永続化するJSファイルの作成
- useStateを使わず、usePersistで定義するとローカルストレージに保存されるようになります。
import React, {useState} from 'react';
const usePersist = (ky, initVal) => {
const key = 'hooks' + ky
const value = () => {
try {
const item = window.localStorage.getItem(key)
return item ? JSON.parse(item) : initVal
} catch (err) {
console.log(err)
return initVal
}
}
const setValue = val => {
try {
setSavedValue(val)
window.localStorage.setItem(key, JSON.stringify(val))
} catch (err) {
console.log(err)
}
}
const [savedValue, setSavedValue] = useState(value)
return [savedValue, setValue]
}
export default usePersist
./memo_comp配下に作成するJSファイル
- memo_compフォルダ配下のexport設定をindex.jsに書きます。
export {default as MemoPage} from './MemoPage'
export {default as AddForm} from './AddForm'
export {default as FindForm} from './FindForm'
export {default as DeleteForm} from './DeleteForm'
export {default as Memo} from './Memo'
export {default as Item} from "./Item"
- 5行目は、各コンポーネントのボタン押下した場合に、10行目をrenderします。
- 6行目は、AddForm(12行目)で追加した内容をMemo(16行目)に反映します。
- 7行目は、FindForm(13行目)で検索した内容をMemo(16行目)に反映します。
import usePersist from '../Persist';
import { AddForm, FindForm, DeleteForm, Memo } from './';
const MemoPage = () => {
const [mode, setMode] = usePersist('mode', 'default')
const [memo, setMemo] = usePersist('memo', [])
const [fmemo, setFMemo] = usePersist('findMemo', [])
return (
<div>
<h5 className='my-3'>mode: {mode}</h5>
<div className='alert alert-primary pb-0'>
<AddForm setMode={setMode} memo={memo} setMemo={setMemo} />
<FindForm setMode={setMode} memo={memo} setFMemo={setFMemo} />
<DeleteForm setMode={setMode} memo={memo} setMemo={setMemo}/>
</div>
<Memo mode={mode} memo={memo} fmemo={fmemo} />
</div>
)
}
export default MemoPage
import React, {useState} from "react"
const AddForm = props => {
const [message, setMessage] = useState('')
const doChange = e => {
setMessage(e.target.value)
}
const doAction = e => {
props.setMode('add')
const data = {
message: message,
created: new Date()
}
let copy_memo = props.memo.concat()
copy_memo.unshift(data)
props.setMemo(copy_memo)
setMessage('')
}
return (
<form>
<div className='form-group row'>
<input type='text' className='form-control-sm col'
onChange={doChange} value={message} required />
<input type='button' onClick={doAction} value='Add' className='btn btn-primary btn-sm col-2' />
</div>
</form>
)
}
export default AddForm
import React, { useState } from "react"
const FindForm = props => {
const [message, setMessage] = useState('')
const doChange = e => {
setMessage(e.target.value)
}
const doAction = e => {
props.setMode('find')
if (message == '') {
props.setFMemo(props.memo)
return //空白の場合、全件表示
}
let res = props.memo.filter((item, key) => {
return item.message.includes(message)
})
props.setFMemo(res)
setMessage('')
}
return (
<form>
<div className='form-group row'>
<input type='text' onChange={doChange}
value={message} className='form-control-sm col' />
<input type='button' onClick={doAction} value='Find' className='btn btn-primary btn-sm col-2' />
</div>
</form>
)
}
export default FindForm
import React, {useState} from "react"
const DeleteForm = props => {
const [num, setNum] = useState(0)
const doChange = e => {
setNum(e.target.value)
}
const doAction = e => {
props.setMode('delete')
let res = props.memo.filter((item, key) => {
return key != num //削除対象以外を抽出
})
props.setMemo(res)
setNum(0)
}
let items = props.memo.map((value, key) => //{}付けない
<option key={key} value={key}>
{value.message.substring(0,10)}
</option>
)
return (
<form>
<div className='form-group row'>
<select onChange={doChange} className='form-control-sm col' defaultValue='-1'>
{items}
</select>
<input type='button' onClick={doAction} value='Del' className='btn btn-primary btn-sm col-2' />
</div>
</form>
)
}
export default DeleteForm
- 8と13行目は、fmemoを使っているかどうかです。switch分岐でなくてもよかったです。
import React from "react"
import {Item} from "./"
const Memo = props => {
let data = []
switch (props.mode) {
case 'find':
data = props.fmemo.map((value, key) => //{}付けない
<Item key={value.message} value={value} index={key + 1} />
)
break
default:
data = props.memo.map((value, key) => //{}付けない
<Item key={value.message} value={value} index={key + 1} />
)
}
return (
<table className='table mt-4'>
<tbody>{data}</tbody>
</table>
)
}
export default Memo
import React from "react"
const Item = props => {
const th = {
width: '100px'
}
const td = {
textAlign: 'right',
width: '150px'
}
let d = new Date(Date.parse(props.value.created))
let f = (d.getMonth()+1) + '/' + d.getDate() + ' ' + d.getHours() + ':' + d.getMinutes()
return (
<tr>
<th style={th}>No, {props.index}</th>
<td>{props.value.message}</td>
<td style={td}>{f}</td>
</tr>
)
}
export default Item
あとがき
特になし
コメントを残す