일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- tailwindcss
- never타입
- Algorithm
- 큐
- JavaScript
- 스택
- cookie
- nestjs
- 버블정렬
- 정렬
- 해쉬
- typscript
- 그리디
- textarea autosize
- aws lightsail
- next.js
- 빅오
- react-query
- TypeScript
- NextAuth
- 투포인터
- 알고리즘
- styled-components
- isNaN
- js알고리즘
- 라이프사이클
- nextjs
- react
- 블로그만들기
- 슬라이딩윈도우
- Today
- Total
far
[React + tailwindCSS] 게시글 이미지 첨부 및 첨부된 이미지 리스트 만들기 본문
쉽게 할 수 있을거라 생각했는데 많이 고생했던 부분이라 기록으로 남긴다. 구조를 짤 때 백엔드단과의 소통 미스로 textarea안에 이미지를 첨부하기 위한 업로드를 짜야하는줄 알고 빙빙 돌아왔기에 더더욱 오래걸렸다.
HTML, 기본 Form
const PostForm = () => {
const imageInput = useRef() as React.RefObject<HTMLInputElement>;
const onClickImageUpload = useCallback(() => {
if (!imageInput.current) {
return;
}
imageInput.current.click();
}, [imageInput.current]);
return (
<div className="flex pl-0 space-x-1 sm:pl-2">
<input type="file"
accept='image/*'
multiple
hidden
ref={imageInput}
onChange={async (e) => {
imageStorageFunction(e)
}}
/>
<div className='flex'>
{
imgl.map((v, i) => {
return (
<div className='flex'>
<img src={v} key={i} data-index={i}
width="75px" height="75px" ref={imageDeleteInput} />
</div>
)
})
}
</div>
<button
className="inline-flex justify-center p-2 text-gray-500 rounded cursor-pointer hover:text-gray-900 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-white dark:hover:bg-gray-600"
onClick={onClickImageUpload}
>
<svg
aria-hidden="true"
className="w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg">
<path fillRule="evenodd" d="M4 3a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V5a2 2 0 00-2-2H4zm12 12H4l4-8 3 6 2-4 3 6z"
clipRule="evenodd"></path>
</svg>
</button>
</div>
)
}
이미지 업로드를 위한 기본적인 틀을 만들었다.
img부분에 src={v}은 백엔드단에서 이미지를 이렇게 받아달라고 하셔서 한 것. 개발 상황에 따라 수정해주면 된다.
이미지 첨부 후 배열에 넣기
const PostForm = () => {
const dispatch = useAppDispatch();
const [imgStorage, setImageStorage] = useState<File[]>([]);
const [imgl, setImgList] = useState<string[]>([]);
const imageInput = useRef() as React.RefObject<HTMLInputElement>;
const imageDeleteInput = useRef<HTMLImageElement | null>(null);
const imageStorageFunction = useCallback((e: ChangeEvent<HTMLInputElement>) => {
if (e.target.files?.length) {
for (let i = 0; i < e.target.files.length; i++) {
const element = e.target.files[i];
setImageStorage(file => [...file, element])
// console.log(element.name);
let reader = new FileReader();
reader.readAsDataURL(element)
reader.onload = (f) => {
setImgList(v => [...v, f.target!.result as string])
}
}
}
e.target.files = null
}, [imgStorage, imgl])
}
일단 이미지를 전송할 때 필요한 값을 받아주는 imageStorage와 이미지 미리보기를 만들 때 필요한 값을 받아주는 imgl을 만든다.
그리고 일단 이미지 데이터를 imageStorage라는 배열에 저장 후 FileReader를 사용해 imgl에서 첨부된 이미지 파일을 읽어올 수 있도록 값을 넣어준다.
(FileReader는 Type이 File인 input 태그 또는 API 요청과 같은 인터페이스를 통해 File 또는 Blob 객체를 입력받아 Text로 읽을 수 있게 해주는 객체이다.
FileReader.readAsDataURL 메서드는 컨텐츠를 특정 Blob 이나 File에서 읽어 오는 역할을 하며,
FileReader.onload는 load 이벤트의 핸들로 읽기 동작이 성공적으로 완료되었을 때마다 발생한다.)
https://developer.mozilla.org/ko/docs/Web/API/FileReader
이제 첨부된 이미지를 게시글 아래에 미리보기처럼 볼 수 있게 되었다.
첨부된 이미지 삭제하기
<div className='flex'>
{
imgl.map((v, i) => {
const deleteImage = () => {
imgl.splice(i, 1)
imgStorage.splice(i, 1)
setImgList(v => [...imgl])
setImageStorage(imgStorage)
}
return (
<div className='flex'>
<img src={v} key={i} data-index={i}
width="75px" height="75px" ref={imageDeleteInput} />
<div className='absolute bg-gray-200 m-1 px-1 '>
<button
type="button"
onClick={deleteImage}
className='flex justify-center items-center text-sm font-medium shadow-md text-purple-500
focus:outline-none border border-gray-200 hover:text-indigo-900 focus:z-10
dark:focus:ring-gray-700 dark:bg-gray-800 dark:text-gray-400 dark:border-gray-600
dark:hover:text-white dark:hover:bg-gray-700'>X</button>
</div>
</div>
)
})
}
</div>
이미지를 배열에서 제거할 때 imgl.map의 index를 사용하기 위해 함수를 여기다 작성했다. 위에다가 작성했더니 state의 변화가 실시간으로 일어나지 않았기 때문에..
imgl의 배열은 읽기 전용 데이터로 들어오기 때문에 setImgList에서 스프래드 연산자를 사용하지 않으면 작동하지 않는다.
import { addPost } from '@actions/post';
...
const onSubmit = useCallback(async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault();
const result = new FormData;
if (imgStorage) {
for (let i = 0; i < imgStorage.length; i++) {
result.append("articleImages", imgStorage[i])
// console.log(imgStorage[i].name);
}
}
dispatch(addPost(result))
}, [])
이제 FormData를 만들어서 미리 redux-toolkit을 사용해 만들어둔 addPost에 dispatch하면 완성이다.
'React > 기록' 카테고리의 다른 글
[React] Key에 대하여 (Map의 key props) (0) | 2023.03.15 |
---|---|
[React] 리액트 메모이제이션 훅 (0) | 2023.03.13 |
[React] 리액트 라이프사이클 (0) | 2022.12.24 |
[React] Loadable-components로 코드 스플리팅 하기 (0) | 2022.12.02 |
Objects are not valid as a React child (found: object with keys {id}). If you meant to render a collection of children, use an array instead. 에러 (0) | 2022.12.01 |