MUIを利用して、画像アップロードコンポーネントを作成します。
- 画像アップロードボタンをクリックすると、input要素標準の画像選択ダイアログを表示
- 単一、もしくは複数の選択した画像をプレビュー表示
- 画像は3カラムで表示
- 右上の×ボタンをクリックすると、プレビュー表示から削除
この画像アップロードコンポーネントで設定された画像データをPOSTすることでサーバーに保存することができるようになります。
本記事ではブラウザ側のメモリにファイルを保存し、プレビュー表示するまでを記載します。
目次
ソース全体
結論、下記の形で実装が可能です。
import React from 'react'
import Button from '@mui/material/Button'
import IconButton from '@mui/material/IconButton'
import CancelIcon from '@mui/icons-material/Cancel'
import { Grid } from '@mui/material'
type Props = {
images: File[]
setImages: (arg: File[]) => void
}
const FileUploader = (props: Props) => {
const maxImagesUpload = 10
const inputId = Math.random().toString(32).substring(2)
const handleOnAddImage = async (e: React.ChangeEvent<HTMLInputElement>) => {
if (!e.target.files) return
const files: File[] = []
for (const file of e.target.files) {
files.push(file)
}
props.setImages([...props.images, ...files])
e.target.value = ''
}
const handleOnRemoveImage = (index: number) => {
const newImages = [...props.images]
newImages.splice(index, 1)
props.setImages(newImages)
}
return (
<>
<Grid container spacing={{ xs: 2, md: 3 }} columns={{ xs: 8, sm: 12, md: 12 }}>
{props.images.map((image, i) => (
<Grid
item
xs={4}
sm={4}
md={4}
key={i}
sx={{
display: 'flex',
justifyContent: 'start',
alignItems: 'center',
position: 'relative'
}}
>
<IconButton
aria-label='delete image'
style={{
position: 'absolute',
top: 10,
right: 0,
color: '#aaa'
}}
onClick={() => handleOnRemoveImage(i)}
>
<CancelIcon />
</IconButton>
<img
src={URL.createObjectURL(image)}
style={{
width: '100%',
height: '100%',
objectFit: 'contain',
aspectRatio: '1 / 1'
}}
alt=''
/>
</Grid>
))}
</Grid>
<label htmlFor={inputId}>
<Button variant='contained' disabled={props.images.length >= maxImagesUpload} component='span' sx={{ mt: 4 }}>
画像アップロード
</Button>
<input
id={inputId}
type='file'
multiple
accept='image/*,.png,.jpg,.jpeg,.gif'
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleOnAddImage(e)}
style={{ display: 'none' }}
/>
</label>
</>
)
}
export default FileUploader
親コンポーネントでuseState<File[]>を定義して、FileUploaderコンポーネントへ渡します。
FileUploaderコンポーネント側で追加されたFileを親コンポーネント側でセットすることで画像のプレビューを行います。
const [images, setImages] = useState<File[]>([])
<FileUploader
images={images}
setImages={setImages}
></FileUploader>
解説
「画像アップロードボタンをクリックすると、input要素標準の画像選択ダイアログを表示」に関しては、input要素のtypeに’file’を指定するだけです。
multiple属性をつけることで複数のファイルを選択できます。
さらにacceptで選択できるファイル拡張子を絞り込んでいます。
選択時に、handleOnAddImage関数を呼び出し、setImagesで画像をメモリに登録します。
<label htmlFor={inputId}>
<Button variant='contained' disabled={props.images.length >= maxImagesUpload} component='span' sx={{ mt: 4 }}>
画像アップロード
</Button>
<input
id={inputId}
type='file'
multiple
accept='image/*,.png,.jpg,.jpeg,.gif'
onChange={(e: React.ChangeEvent<HTMLInputElement>) => handleOnAddImage(e)}
style={{ display: 'none' }}
/>
</label>
setImagesに画像を登録すると、コンポーネントが再描画されるので、mapでまわし、URL.createObjectURLで画像を表示しています。
{props.images.map((image, i) => (
<img src={URL.createObjectURL(image)}/>
))}
「右上の×ボタンをクリックすると、プレビュー表示から削除」では、handleOnRemoveImageでクリックされた画像のIndexを渡してimagesのメモリ上から削除し、setimagesしなおしています。
まとめ
MUIを利用するとデザインは簡易になるので、ロジック面だけに集中できるのはメリットです。
画像アップロードもFileインターフェースの扱いが戸惑いそうなところですが、MDNを読むとすんなりと理解が進みそうです。