강의

멘토링

로드맵

Inflearn brand logo image

인프런 커뮤니티 질문&답변

DDU님의 프로필 이미지
DDU

작성한 질문수

[리뉴얼] React로 NodeBird SNS 만들기

이미지 업로드 시 data가 보내지지 않습니다..ㅠㅠ

작성

·

328

0

안녕하세요. 강사님! 강의를 들으며 똑같이 작성했고 혹시 모를 오타가 있는지도 확인하면서 강사님 깃허브도 참조해보았는데 원인을 찾지 못하겠어서 문의드립니다. ㅠ ㅠ

이미지 업로드를 하면 아래와 같이 오류가 뜨고 redux dev tools 로 확인해 보았을 때
UPLOAD_IMAGES_REQUEST 만 처리되고 SUCCESS 로 넘어가지가 않아요.
리퀘스트 되어 있는 데이터를 보면 빈 값인 것을 보니 데이터가 넘어가지 않는 것 같은데 도무지 원인을 모르겠습니다. 업로드를 하면 에러가 나면서 서버연결도 끊깁니다. ㅜㅜ

제 코드에 뭐가 문제가 있는 건가요..?ㅠㅠ
확인 후 문제를 알려주시면 너무 감사할 것 같습니다..ㅠㅠㅠ

[redux]


import produce from "immer";

export const initialState = {
  mainPosts: [],
  // 이미지 업로드할 때 이미지의 경로들이 저장됨
  imagePaths: [],
  uploadImagesLoading: false,
  uploadImagesDone: false,
  uploadImagesError: null,
}


export const UPLOAD_IMAGES_REQUEST = "UPLOAD_IMAGES_REQUEST";
export const UPLOAD_IMAGES_SUCCESS = "UPLOAD_IMAGES_SUCCESS";
export const UPLOAD_IMAGES_FAILURE = "UPLOAD_IMAGES_FAILURE";

const reducer = (state = initialState, action) => {
  return produce(state, (draft) => {
    switch (action.type) {
      case UPLOAD_IMAGES_REQUEST:
        draft.uploadImagesLoading = true;
        draft.uploadImagesDone = false;
        draft.uploadImagesError = null;
        break;
      case UPLOAD_IMAGES_SUCCESS: 
        draft.imagePaths = action.data;
        draft.uploadImagesLoading = false;
        draft.uploadImagesDone = true;
        break;
      case UPLOAD_IMAGES_FAILURE:
        draft.uploadImagesLoading = false;
        draft.uploadImagesError = action.error;
        break;
      default:
        break;
    }
  });
};

export default reducer;

[saga]

import axios from "axios";

import {
  all,
  fork,
  put,
  takeLatest,
  throttle,
  call,
} from "redux-saga/effects";

import {
  UPLOAD_IMAGES_FAILURE,
  UPLOAD_IMAGES_REQUEST,
  UPLOAD_IMAGES_SUCCESS,
} from "../reducers/post";


function uploadImagesAPI(data) {
  return axios.post("/post/images", data);
}

function* uploadImages(action) {
  try {
    const result = yield call(uploadImagesAPI, action.data);
    yield put({
      type: UPLOAD_IMAGES_SUCCESS,
      data: result.data,
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: UPLOAD_IMAGES_FAILURE,
      error: err.response.data,
    });
  }
}

function* watchUploadImages() {
  yield takeLatest(UPLOAD_IMAGES_REQUEST, uploadImages);
}

export default function* postSaga() {
  yield all([
    fork(watchUploadImages),
  ]);
}

[components]


import React, { useCallback, useEffect, useRef } from "react";
import { useSelector, useDispatch } from "react-redux";
import { Form, Input, Button } from "antd";
import {
  UPLOAD_IMAGES_REQUEST,
  REMOVE_IMAGE,
  ADD_POST_REQUEST,
} from "../reducers/post";
import useInput from "../hooks/useInput";

const PostForm = () => {
  const dispatch = useDispatch();
  const { imagePaths, addPostDone } = useSelector((state) => state.post);
  const [text, onChangeText, setText] = useInput("");
  const imageInput = useRef();

  useEffect(() => {
    if (addPostDone) {
      setText("");
    }
  }, [addPostDone]);

  const onSubmit = useCallback(() => {
    if (!text || !text.trim()) {
      return alert("게시글을 작성하세요.");
    }
    const formData = new FormData();
    imagePaths.forEach((p) => {
      formData.append("image", p);
    });
    formData.append("content", text);
    return dispatch({
      type: ADD_POST_REQUEST,
      data: formData,
    });
  }, [text, imagePaths]);

  const onClickImageUpload = useCallback(() => {
    imageInput.current.click();
  }, [imageInput.current]);

  const onChangeImages = useCallback((e) => {
    console.log("images", e.target.files);
    const imageFormData = new FormData();
    [].forEach.call(e.target.files, (f) => {
      imageFormData.append("image", f);
    });
    dispatch({
      type: UPLOAD_IMAGES_REQUEST,
      data: imageFormData,
    });
  }, []);

  const onRemoveImage = useCallback(
    (index) => () => {
      dispatch({
        type: REMOVE_IMAGE,
        data: index,
      });
    },
    []
  );

  return (
    <Form
      style={{ margin: "10px 0 20px" }}
      encType="multipart/form-data"
      onFinish={onSubmit}
    >
      <Input.TextArea
        value={text}
        onChange={onChangeText}
        maxLength={140}
        placeholder="어떤 신기한 일이 있었나요?"
      />
      <div>
        <input
          type="file"
          name="image"
          multiple
          hidden
          ref={imageInput}
          onChange={onChangeImages}
        />
        <Button onClick={onClickImageUpload}>이미지 업로드</Button>
        <Button type="primary" style={{ float: "right" }} htmlType="submit">
          짹짹
        </Button>
      </div>
      <div>
        {imagePaths?.map((v, i) => (
          <div key={v} style={{ display: "inline-block" }}>
            <img
              src={`http://localhost:3065/${v}`}
              alt={v}
              style={{ width: "200px" }}
            />
            <div>
              <Button onClick={onRemoveImage(i)}>제거</Button>
            </div>
          </div>
        ))}
      </div>
    </Form>
  );
};

export default PostForm;

 

[back - routes/post.js]

const express = require("express");
const multer = require("multer");
const path = require("path");
const fs = require("fs");

const { Post, Image, Comment, User } = require("../models");
const { isLoggedIn } = require("./middlewares");

const router = express.Router();

try {
  fs.accessSync("uploads");
} catch (error) {
  console.log("uploads 폴더가 없으므로 생성합니다.");
  fs.mkdirSync("uploads");
}

const upload = multer({
  storage: multer.diskStorage({
    destination(req, file, done) {
      done(null, "uploads");
    },
    filename(req, file, done) {
      // amanda.png
      const ext = path.extname(file.originalname); // 확장자 추출(.png)
      const basename = path.basename(file.originalname, ext); // amanda
      done(null, basename + "_" + new Data().getTiem() + ext); // amanda15653484.png
    },
  }),
  limits: { fileSize: 20 * 1024 * 1024 }, // 20MB
});

router.post(
  "/images",
  isLoggedIn,
  upload.array("image"), // single , array , none
  async (req, res, next) => {
    // POST /post/images
    console.log(req.files);
    res.json(req.files.map((v) => v.filename));
  }
);

router.post("/", isLoggedIn, upload.none(), async (req, res) => {
  // POST /post
  try {
    const post = await Post.create({
      content: req.body.content,
      UserId: req.user.id,
    });
    if (req.body.image) {
      if (Array.isArray(req.body.image)) {
        // 이미지를 여러 개 올리면 image: [제로초.png, 부기초.png]
        const images = await Promise.all(
          req.body.image.map((image) => Image.create({ src: image }))
        );
        await post.addImages(images);
      } else {
        // 이미지를 하나만 올리면 image: 제로초.png
        const image = await Image.create({ src: req.body.image });
        await post.addImages(image);
      }
    }
    const fullPost = await Post.findOne({
      where: { id: post.id },
      include: [
        {
          model: Image,
        },
        {
          model: Comment,
          include: [
            {
              model: User, // 댓글 작성자
              attributes: ["id", "nickname"],
            },
          ],
        },
        {
          model: User, // 게시글 작성자
          attributes: ["id", "nickname"],
        },
        {
          model: User, // 좋아요 누른 사람
          as: "Likers",
          attributes: ["id"],
        },
      ],
    });
    res.status(201).json(fullPost);
  } catch (error) {
    console.error(error);
    next(error);
  }
});



module.exports = router;

답변 1

0

제로초(조현영)님의 프로필 이미지
제로초(조현영)
지식공유자

백엔드서버를 안 켜신 겁니다.

DDU님의 프로필 이미지
DDU
질문자

백엔드 서버를 키고 업로드 시키니 앱크러쉬가 나서 코드 다시 확인해보고 오류 잡았어요 :) 감사합니다ㅣ.

DDU님의 프로필 이미지
DDU

작성한 질문수

질문하기