FullStackOpen Part5-b props.children and proptypes メモ
Displaying the login form only when appropriate
スタイルdisplayを使用して表示・非表示をトグルできる
const App = () => {
const [loginVisible, setLoginVisible] = useState(false)
// ...
const loginForm = () => {
const hideWhenVisible = { display: loginVisible ? 'none' : '' }
const showWhenVisible = { display: loginVisible ? '' : 'none' }
return (
<div>
<div style={hideWhenVisible}>
<button onClick={() => setLoginVisible(true)}>log in</button>
</div>
<div style={showWhenVisible}>
<LoginForm
username={username}
password={password}
handleUsernameChange={({ target }) => setUsername(target.value)}
handlePasswordChange={({ target }) => setPassword(target.value)}
handleSubmit={handleLogin}
/>
<button onClick={() => setLoginVisible(false)}>cancel</button>
</div>
</div>
)
}
// ...
}
The components children, aka props.children
Togglableを定義して、トグルする機能をコンポーネント化する
import { useState } from 'react'
const Togglable = (props) => {
const [visible, setVisible] = useState(false)
const hideWhenVisible = { display: visible ? 'none' : '' }
const showWhenVisible = { display: visible ? '' : 'none' }
const toggleVisibility = () => {
setVisible(!visible)
}
return (
<div>
<div style={hideWhenVisible}>
<button onClick={toggleVisibility}>{props.buttonLabel}</button>
</div>
<div style={showWhenVisible}>
{props.children}
<button onClick={toggleVisibility}>cancel</button>
</div>
</div>
)
}
export default Togglable
State of the forms
最も近い共通の親にステートを持たせる
以下のようにNoteFormとAppの間ではaddNoteのみをやり取りさせている
import { useState } from 'react'
const NoteForm = ({ createNote }) => {
const [newNote, setNewNote] = useState('')
const addNote = (event) => {
event.preventDefault()
createNote({
content: newNote,
important: true
})
setNewNote('')
}
return (
<div>
<h2>Create a new note</h2>
<form onSubmit={addNote}>
<input
value={newNote}
onChange={event => setNewNote(event.target.value)}
/>
<button type="submit">save</button>
</form>
</div>
)
}
export default NoteForm
const App = () => {
// ...
const addNote = (noteObject) => {
noteService
.create(noteObject)
.then(returnedNote => {
setNotes(notes.concat(returnedNote))
})
}
// ...
const noteForm = () => (
<Togglable buttonLabel='new note'>
<NoteForm createNote={addNote} />
</Togglable>
)
// ...
}
References to components with ref
NoteFormでsubmitした後にTogglableでvisibleを変更するのは難しい
その時に使うのがuseRef。Re-renderをした後でもコンポーネントへの参照を維持できる
useImperativeとforwardRefを使い、NoteForm中のtoggleVisibility()を外からでも呼び出せるように設定。
noteFormRef.current.toggleVisibility()のようにアクセス
import { useState, useEffect, useRef } from 'react'
const App = () => {
// ...
const noteFormRef = useRef()
const noteForm = () => (
<Togglable buttonLabel='new note' ref={noteFormRef}>
<NoteForm createNote={addNote} />
</Togglable>
)
// ...
}
import { useState, forwardRef, useImperativeHandle } from 'react'
const Togglable = forwardRef((props, refs) => {
const [visible, setVisible] = useState(false)
const hideWhenVisible = { display: visible ? 'none' : '' }
const showWhenVisible = { display: visible ? '' : 'none' }
const toggleVisibility = () => {
setVisible(!visible)
}
useImperativeHandle(refs, () => {
return {
toggleVisibility
}
})
return (
<div>
<div style={hideWhenVisible}>
<button onClick={toggleVisibility}>{props.buttonLabel}</button>
</div>
<div style={showWhenVisible}>
{props.children}
<button onClick={toggleVisibility}>cancel</button>
</div>
</div>
)
})
export default Togglable
const App = () => {
// ...
const addNote = (noteObject) => {
noteFormRef.current.toggleVisibility()
noteService
.create(noteObject)
.then(returnedNote => {
setNotes(notes.concat(returnedNote))
})
}
// ...
}
One point about components
コンポーネントを定義した場合以下のように使う
const Togglable = () => ...
// ...
}
<div>
<Togglable buttonLabel="1" ref={togglable1}>
first
</Togglable>
<Togglable buttonLabel="2" ref={togglable2}>
second
</Togglable>
<Togglable buttonLabel="3" ref={togglable3}>
third
</Togglable>
</div>
The updated full stack developer's oath
フロントエンドにバグがありそうなら、まずはバックエンドが動くか確認する
バックエンドにバグがありそうなら、まずはフロントエンドが動くか確認する
演習メモ
ブログをlikesの数でソートするときは以下のようにする
const blogList = () => {
return (
<>
<h2>Amazing blogs!</h2>
<div>
{
blogs.sort((a, b) => b.likes - a.likes).map(blog =>
<Blog key={blog.id} blog={blog} updateBlog={updateBlog} removeBlog={removeBlog} user={user} />
)
}
</div>
</>
)
}
PropTypes
propをrequiredにするための機能としてPropTypesが使える
npm install prop-types
以下のように定義
import PropTypes from 'prop-types'
const Togglable = React.forwardRef((props, ref) => {
// ..
})
Togglable.propTypes = {
buttonLabel: PropTypes.string.isRequired
}
import PropTypes from 'prop-types'
const LoginForm = ({
handleSubmit,
handleUsernameChange,
handlePasswordChange,
username,
password
}) => {
// ...
}
LoginForm.propTypes = {
handleSubmit: PropTypes.func.isRequired,
handleUsernameChange: PropTypes.func.isRequired,
handlePasswordChange: PropTypes.func.isRequired,
username: PropTypes.string.isRequired,
password: PropTypes.string.isRequired
}
ESlint
create-react-appで作成すると自動でESlintがついてくる
eslint --initコマンドは実行しない(最新バージョンになり互換性が失われるため)