概要
dialogやsnackbarをmaterialで記載する時にcontextを利用すると再利用生があがるので、初期開発の速度が上がる可能性がある。
経緯
個人的にも昔のProjectで、[ダイアログ空いてる?、] = useState()てきなことを大量にかいていた戒め。
TL;DR
ReactのClasss Componentを利用してContext Providerを作成。
component側からは、DialogのReactElementを渡す。
ClassComponentを使わない場合については、hooksが利用できないので(hooks in hooksでエラー)死んだ話。
流れ
- Reactのプロジェクトを作成
- Materialを追加
- Class Componentを利用してContextを作成
- Dialogの中身を作成(適当に)
- Functional Componen側からDialogContextを呼ぶ
1. Reactのプロジェクトを作成
Nxまたはcreate-react-appを利用してアプリケーションを作成
create-react-appを利用する場合
$ create-react-app my-app
nxを利用する場合
terminal
$ npx create-nx-workspace@latest --preset=react
プロジェクトが作成されたら、Materialの追加
2. Materialを追加
$ yarn add @material-ui/core
ここで下準備終了。
3. Class Componentを利用してContextを作成
stateにDialogに必要なopenのflag, elementを利用、
contextValueのcreate, closeのfuncitonを持たせる。
dialogにoptionを追加した場合は、default のoptionを内部で持ちcreateの時にoverrideするといろいろ楽に対応できます。
import { Dialog } from '@material-ui/core';
import React, { createContext } from 'react';
// ?はdefaultValueの時に渡せないため、?を利用
export interface DialogContextValue {
createDialog?: (elm: JSX.Element) => void,
closeDialog?: () => void,
open: boolean
}
/* eslint-disable-next-line */
export interface DialogContexttProps {}
interface DialogContextState {
open: boolean,
elm: JSX.Element
}
export const DialogContext = createContext<DialogContextValue>({
open: false
})
export class DialogcontextProvider extends React.Component<DialogContexttProps, DialogContextState> {
constructor(props) {
super(props)
this.state = {
open: false,
elm: null
}
}
createDialog = (elm: JSX.Element) => {
this.setState(state => ({
...state,
open: true,
elm
}))
}
closeDialog = () => {
this.setState(state => ({
...state,
open: false,
elm: null
}))
}
render() {
return (
<DialogContext.Provider value={{
open: this.state.open,
createDialog: this.createDialog,
closeDialog: this.closeDialog
}}>
{ this.props.children }
<Dialog open={this.state.open}>
{ this.state.elm }
</Dialog>
</DialogContext.Provider>
)
}
}
export default DialogcontextProvider;
次にcontextを利用するため、Appに追記します。
適宜利用してください。
import React from 'react';
import DialogcontextProvider from './contexts/dialog-contextt';
const App = () => (
<div>
<DialogcontextProvider>
<div>sample</div>
</DialogcontextProvider>
</div>
)
export default App
4. Dialogの中身を作成(適当に)
これは、適当でいいですがhooksを内部で利用するとclass Componentじゃないといけないことがわかると思います。
useForm, useDispatch, useSelectorなど結構メジャーどころを使ってcontextProvider ClassComponentじゃない時に、hook in hookのエラーが表示されます。
import React from 'react';
/* eslint-disable-next-line */
export interface DialogContentProps {}
export function DialogContent(props: DialogContentProps) {
return (
<div>
<h1>Welcome to dialog-content!</h1>
</div>
);
}
export default DialogContent;
5. Functional Componen側からDialogContextを呼ぶ
最後に呼び出し元のcomponentを作成して、dialog呼べるか確認していきます。
import { DialogContent } from './dialog-content';
import React, { useContext } from 'react';
import { DialogContext } from '../contexts/dialog-contextt';
/* eslint-disable-next-line */
export interface ButtonProps {}
export function Button(props: ButtonProps) {
const { createDialog } = useContext(DialogContext)
return (
<div>
<button onClick={() => createDialog(<DialogContent />)}>click to open</button>
</div>
);
}
export default Button;
app.tsxに呼び出し元を追加。
import React from 'react';
import Button from './components/button';
import DialogcontextProvider from './contexts/dialog-contextt';
const App = () => (
<div>
<DialogcontextProvider>
<div>sample</div>
<Button />
</DialogcontextProvider>
</div>
)
export default App
表示されたbuttonをクリックした時にelementが表示されます。
地味に利用手段が増えるので、snackbarとかも、入れておくと、
function Sample () {
useEffect(() => {
createSnackBar({ type: 'error', message: "Api Failっぽいよ" })
}, [errors])
return (
)
}
の様にcomponent側から呼び出すことが可能になり、Componentは一つのため、開発速度は上がります。