1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【React】アップロード画像がプレビュー表示されないエラー解決方法

Posted at

はじめに

アップロードした画像をプレビュー表示する実装中、画像が表示されないエラーにつまずきましたので記事にします。


【修正前のコード】
.tsx
import React, { ChangeEvent, ChangeEventHandler, useState } from 'react'

type PostParams = {
  title: string,
  content: string,
  img: string | File | undefined
}

export const PostRegister = () => {
  const [ title, setTitle] = useState<string>("");
  const [ content, setContent ] = useState<string>("");
  const [ img, setImg ] = useState<string | File | undefined>(); 
  const [ posts, setPosts ] = useState<PostParams[]>([]);

  const handleFileChange: ChangeEventHandler<HTMLInputElement> = (e) => {
    if (e.target.files && e.target.files[0]) {
      const file = e.target.files[0]; // Fileオブジェクトが作成されている
      if (!file) return;
      const fileImage = window.URL.createObjectURL(file); // 一時的なURL作成 (blob: <画像のURL>)が作成されている
      setImg(fileImage); // set関数に画像URLを渡す
      window.URL.revokeObjectURL(fileImage); // URLを解放する
    }
  }

  const handleSubmit = () => {
    const newPosts = {
      title: title,
      content: content,
      img: img
    }

    setPosts((prevPosts) => {
      return [...prevPosts, newPosts]
    
    })
  }

  return (
    <div className='flex flex-col items-center space-y-4'>
      <div className='text-3xl font-bold'>投稿登録ページ</div>
      <label className='text-xl'>タイトル</label>
      <input
        className='bg-gray-50 border border-gray-300 rounded-lg flex p-2.5'
        type='text'
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <label className='text-xl'>内容</label>
      <textarea
        className='border flex'
        value={content}
        onChange={(e) => setContent(e.target.value)}
      />
      <label className='text-xl'>画像</label>
      <input
        className='flex border'
        type='file'
        accept='image/*'
        onChange={handleFileChange}
      />

      {typeof img === 'string' && (
        <img
          className='flex'
          style={{ width: 200, height: 200 }}
          src={img}
        />
      )}
      <button
        className='bg-gray-50 border border-gray-300 rounded-lg p-2.5'
        type='submit'
        onClick={handleSubmit}
      >
        送信
      </button>

      {/* 仮投稿一覧 */}
      <h1>仮投稿一覧</h1>
      <div>
        {posts.map((post, index) => {
          return (
            <div key={index}>
              <p>{post.title}</p>
              <p>{post.content}</p>
              {typeof post.img === 'string' && (
                <img src={post.img}></img>
              )}
            </div>
          )
        })}
      </div>

    </div>
  )
}

解決方法

useEffectでアイマウント時にURL開放するよう修正

【修正コード】

.tsx
import React, { ChangeEvent, ChangeEventHandler, useEffect, useState } from 'react'

type PostParams = {
  title: string,
  content: string,
  img: string | File | undefined
}

export const PostRegister = () => {
  const [ title, setTitle] = useState<string>("");
  const [ content, setContent ] = useState<string>("");
  const [ img, setImg ] = useState<string | File | undefined>(); 
  const [ posts, setPosts ] = useState<PostParams[]>([]);
  
  // アイマウント時またはimgが変更された場合に実施
  useEffect(() => {
    return () => {
      if (typeof img === 'string') {
        window.URL.revokeObjectURL(img); // URLを解放する
      }
    };
  }, [img]);

  const handleFileChange: ChangeEventHandler<HTMLInputElement> = (e) => {
    if (e.target.files && e.target.files[0]) {
      const file = e.target.files[0]; // Fileオブジェクトが作成されている
      if (!file) return;
      const fileImage = window.URL.createObjectURL(file); // 一時的なURL作成 (blob: <画像のURL>)が作成されている
      setImg(fileImage); // set関数に画像URLを渡す
    }
  }

  const handleSubmit = () => {
    const newPosts = {
      title: title,
      content: content,
      img: img
    }

    setPosts((prevPosts) => {
      return [...prevPosts, newPosts]
    
    })
  }

  return (
    <div className='flex flex-col items-center space-y-4'>
      <div className='text-3xl font-bold'>投稿登録ページ</div>
      <label className='text-xl'>タイトル</label>
      <input
        className='bg-gray-50 border border-gray-300 rounded-lg flex p-2.5'
        type='text'
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />
      <label className='text-xl'>内容</label>
      <textarea
        className='border flex'
        value={content}
        onChange={(e) => setContent(e.target.value)}
      />
      <label className='text-xl'>画像</label>
      <input
        className='flex border'
        type='file'
        accept='image/*'
        onChange={handleFileChange}
      />

      {typeof img === 'string' && (
        <img
          className='flex'
          style={{ width: 200, height: 200 }}
          src={img}
        />
      )}
      <button
        className='bg-gray-50 border border-gray-300 rounded-lg p-2.5'
        type='submit'
        onClick={handleSubmit}
      >
        送信
      </button>

      {/* 仮投稿一覧 */}
      <h1>仮投稿一覧</h1>
      <div>
        {posts.map((post, index) => {
          return (
            <div key={index}>
              <label>タイトル</label>
              <p>{post.title}</p>
              <label>内容</label>
              <p>{post.content}</p>
              {typeof post.img === 'string' && (
                <img
                  className='flex'
                  src={post.img}
                  style={{ width: 200, height: 200}}
                />
              )}
            </div>
          )
        })}
      </div>
    </div>
  )
}

参考

おわりに

クリーンアップ関数について学ぶことができました。


1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?