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

lcm2822님의 프로필 이미지

작성한 질문수

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

게시글, 댓글 작성하기

게시글 작성 오류입니다!

해결된 질문

작성

·

524

0

늘 좋은 강의를 제공해주시는 제로초님

로그인을 성공적으로 한 다음 게시글을 작성하려는데 숫자이든 글자이든 하나만 입력해도 바로 에러가 발생합니다.

객체나 배열을 주고받는 과정에서 에러가 발생했다고 생각하여 구글링과 여러 코드를 비교해보았지만 

문제점을 찾지 못했습니다.

피드백을 받고싶습니다. 늘 감사합니다!

답변 14

1

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

아, 배열이니까 당연히 순서가 중요합니다. [1,2,3]이랑 [1,3,2]랑은 다르니까요.

0

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

오랜시간 도와주셔서 감사합니다!

덕분에 좋은 공부가 됬습니다!

0

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

제로초님 

import부분에서 

const [textsetTextonChangeText] = useInput('');
const [textonChangeTextsetText] = useInput('');
위 부분을 아래처럼 바꾸었더니
기존에 콘솔에서 아무것도 찍히지 않았던 콘솔이 아래의 그림처럼 나오고 이제 정상적으로 타이핑이 가능합니다!
혹시 이유를 알 수 있을까요?

0

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

아이디 비밀번호가 문제인 상황이 아니라 게시글 입력이 문제인 상황 아니신가요? 게시글 입력 시를 콘솔이 어떻게 뜨는지를 보여주셔야 합니다.

0

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

useInput.js

import { useStateuseCallback } from 'react';

export default (initValue = null=> {
  const [valuesetter] = useState(initValue);
  const handler = useCallback((e=> {
    console.log('문제점 시작');
    console.log(e.target.value);
    console.log('문제점 끝');
    setter(e.target.value);
  }, []);
  return [valuehandlersetter];
};
value={text}를 넣었을 때 콘솔입니다.
value={text}를 주석처리했을 때 콘솔입니다.
아이디와 비밀번호 둘 다 정상적으로 나왔습니다.

0

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

네 value 넣는게 문제라는 것을 알고 있고요. 그러니까 e.target.value를 console.log 하는게 매우 중요합니다. 그걸 보여주셔야 해요.

0

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

input.TextArea 부분의 문제로 판단됩니다.

<Input.TextArea
      value={text}
      onChange={onChangeText}
      maxLength={140}
      placeholder="어떤 신기한 일이 있었나요?"
    />
이 코드에서
1.maxLength 부분을 제거하면 처음 입력 했을 때 아래의 그림처럼 나오고 다른 입력이 불가능합니다.
2. value={text}부분을 제거하면 정상적으로 아래의 그림처럼 나타납니다.
value={text} 부분이 문제가 있다고 판단했습니다. 혹시 value값을 대체허거나 제거할 수 있을까요?

0

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

setter(e.target.value) 위에 console.log(e.target.value) 넣으셔서 onChange시 값이 들어오는지 확인해보세요.

다시 태그들을 하나씩 넣어서 어떤 태그나 props를 추가할 때 문제가 되는지 확인해보세요.

0

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

제가 이해하는 것에 문제가 있어 return 부분에서  useInput부분수정을 못 하였으나 Input.Textarea시도하는 것은 해보았는데

글씨 작성이 되었습니다!

혹시 몰라서 코드 올려봅니다.

useInput.js

import { useStateuseCallback } from 'react';

export default (initValue = null=> {
  const [valuesetter] = useState(initValue);
  const handler = useCallback((e=> {
    setter(e.target.value);
  }, []);
  return [valuehandlersetter];
};
PorstForm.js return부분
return (
    
      <Input.TextArea />
 ); 
타자가 쳐지는 그림

0

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

코드 상으로는 문제가 잘 안 보이는데요. useInput.js 가셔서 onChange에서 setValue 위에 console.log(e.target.value) 찍어보세요. 그리고 return 부분에 Form 같은 다른 거 다 빼고 Input.Textarea만 남기고 시도해보세요.

0

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

post관련 front 코드입니다.

PostForm.js

import React, { useCallbackuseEffectuseRef } from 'react';
import { FormInputButton } from 'antd';
import { useSelectoruseDispatch } from 'react-redux';

import { addPost } from '../reducers/post';
import useInput from '../hooks/useInput';
const PostForm = () => {
  const dispatch = useDispatch();
  const [textsetTextonChangeText] = useInput('');
  const { imagePathsaddPostLoadingaddPostDone } = useSelector((state=> state.post);

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

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

  const onSubmit = useCallback(() => {
    dispatch(addPost(text));
  }, [text]);

  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" multiple hidden ref={imageInput} />
        <Button onClick={onClickImageUpload}>이미지 업로드</Button>
        <Button type="primary" style={float: 'right' }} htmlType="submit" loading={addPostLoading}>짹짹</Button>
      </div>
      <div>
        {imagePaths.map((v=> (
          <div key={v} style={display: 'inline-block' }}>
            <img src={`http://localhost:3065/${v}`} style={width: '200px' }} alt={v} />
            <div>
              <Button>제거</Button>
            </div>
          </div>
        ))}
      </div>
    </Form>
  );
};

export default PostForm;
sagas/post.js
import axios from 'axios';
import { call,alldelayforkputtakeLatestthrottle } from 'redux-saga/effects';

import {
  ADD_COMMENT_FAILURE,
  ADD_COMMENT_REQUEST,
  ADD_COMMENT_SUCCESS,
  ADD_POST_FAILURE,
  ADD_POST_REQUEST,
  ADD_POST_SUCCESS,
  generateDummyPost,
  LOAD_POSTS_FAILURE,
  LOAD_POSTS_REQUEST,
  LOAD_POSTS_SUCCESS,
  REMOVE_POST_FAILURE,
  REMOVE_POST_REQUEST,
  REMOVE_POST_SUCCESS,
from '../reducers/post';
import { ADD_POST_TO_MEREMOVE_POST_OF_ME } from '../reducers/user';

function loadPostsAPI(data) {
  return axios.get('/api/posts'data);
}

function* loadPosts(action) {
  try {
    // const result = yield call(loadPostsAPI, action.data);
    yield delay(1000);
    yield put({
      type: LOAD_POSTS_SUCCESS,
      data: generateDummyPost(10),
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: LOAD_POSTS_FAILURE,
      data: err.response.data,
    });
  }
}

function addPostAPI(data) {
  return axios.post('/post',  data);
}

function* addPost(action) {
  try {
    const result = yield call(addPostAPIaction.data);
    yield put({
      type: ADD_POST_SUCCESS,
      data: result.data,
    });
    yield put({
      type: ADD_POST_TO_ME,
      data: result.data.id,
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: ADD_POST_FAILURE,
      data: err.response.data,
    });
  }
}

function removePostAPI(data) {
  return axios.delete('/api/post'data);
}

function* removePost(action) {
  try {
    // const result = yield call(removePostAPI, action.data);
    yield delay(1000);
    yield put({
      type: REMOVE_POST_SUCCESS,
      data: action.data,
    });
    yield put({
      type: REMOVE_POST_OF_ME,
      data: action.data,
    });
  } catch (err) {
    console.error(err);
    yield put({
      type: REMOVE_POST_FAILURE,
      data: err.response.data,
    });
  }
}

function addCommentAPI(data) {
  return axios.post(`/post/${data.postId}/comment`data);
}

function* addComment(action) {
  try {
    const result = yield call(addCommentAPIaction.data);
    yield put({
      type: ADD_COMMENT_SUCCESS,
      data: result.data,
    });
  } catch (err) {
    yield put({
      type: ADD_COMMENT_FAILURE,
      data: err.response.data,
    });
  }
}

function* watchLoadPosts() {
  yield throttle(5000LOAD_POSTS_REQUESTloadPosts);
}

function* watchAddPost() {
  yield takeLatest(ADD_POST_REQUESTaddPost);
}

function* watchRemovePost() {
  yield takeLatest(REMOVE_POST_REQUESTremovePost);
}

function* watchAddComment() {
  yield takeLatest(ADD_COMMENT_REQUESTaddComment);
}

export default function* postSaga() {
  yield all([
    fork(watchAddPost),
    fork(watchLoadPosts),
    fork(watchRemovePost),
    fork(watchAddComment),
  ]);
}
reducers/post.js
import shortId from 'shortid';
import faker from 'faker';

import produce from '../util/produce';

export const initialState = {
  mainPosts: [],
  imagePaths: [],
  hasMorePosts: true,
  loadPostsLoading: false,
  loadPostsDone: false,
  loadPostsError: null,
  addPostLoading: false,
  addPostDone: false,
  addPostError: null,
  removePostLoading: false,
  removePostDone: false,
  removePostError: null,
  addCommentLoading: false,
  addCommentDone: false,
  addCommentError: null,
};

export const generateDummyPost = (number=> Array(number).fill().map(() => ({
  id: shortId.generate(),
  User: {
    id: shortId.generate(),
    nickname: faker.name.findName(),
  },
  content: faker.lorem.paragraph(),
  Images: [{
    src: faker.image.image(),
  }],
  Comments: [{
    User: {
      id: shortId.generate(),
      nickname: faker.name.findName(),
    },
    content: faker.lorem.sentence(),
  }],
}));

export const LOAD_POSTS_REQUEST = 'LOAD_POSTS_REQUEST';
export const LOAD_POSTS_SUCCESS = 'LOAD_POSTS_SUCCESS';
export const LOAD_POSTS_FAILURE = 'LOAD_POSTS_FAILURE';

export const ADD_POST_REQUEST = 'ADD_POST_REQUEST';
export const ADD_POST_SUCCESS = 'ADD_POST_SUCCESS';
export const ADD_POST_FAILURE = 'ADD_POST_FAILURE';

export const REMOVE_POST_REQUEST = 'REMOVE_POST_REQUEST';
export const REMOVE_POST_SUCCESS = 'REMOVE_POST_SUCCESS';
export const REMOVE_POST_FAILURE = 'REMOVE_POST_FAILURE';

export const ADD_COMMENT_REQUEST = 'ADD_COMMENT_REQUEST';
export const ADD_COMMENT_SUCCESS = 'ADD_COMMENT_SUCCESS';
export const ADD_COMMENT_FAILURE = 'ADD_COMMENT_FAILURE';

export const addPost = (data=> ({
  type: ADD_POST_REQUEST,
  data,
});

export const addComment = (data=> ({
  type: ADD_COMMENT_REQUEST,
  data,
});

// 이전 상태를 액션을 통해 다음 상태로 만들어내는 함수(불변성은 지키면서)
const reducer = (state = initialStateaction=> produce(state, (draft=> {
  switch (action.type) {
    case LOAD_POSTS_REQUEST:
      draft.loadPostsLoading = true;
      draft.loadPostsDone = false;
      draft.loadPostsError = null;
      break;
    case LOAD_POSTS_SUCCESS:
      draft.loadPostsLoading = false;
      draft.loadPostsDone = true;
      draft.mainPosts = draft.mainPosts.concat(action.data);
      draft.hasMorePosts = action.data.length === 10;
      //draft.mainPosts = action.data.concat(draft.mainPosts);
      //draft.hasMorePosts = draft.mainPosts.length < 50;
      break;
    case LOAD_POSTS_FAILURE:
      draft.loadPostsLoading = false;
      draft.loadPostsError = action.error;
      break;
    case ADD_POST_REQUEST:
      draft.addPostLoading = true;
      draft.addPostDone = false;
      draft.addPostError = null;
      break;
    case ADD_POST_SUCCESS:
      draft.addPostLoading = false;
      draft.addPostDone = true;
      draft.mainPosts.unshift(action.data);
      break;
    case ADD_POST_FAILURE:
      draft.addPostLoading = false;
      draft.addPostError = action.error;
      break;
    case REMOVE_POST_REQUEST:
      draft.removePostLoading = true;
      draft.removePostDone = false;
      draft.removePostError = null;
      break;
    case REMOVE_POST_SUCCESS:
      draft.removePostLoading = false;
      draft.removePostDone = true;
      draft.mainPosts = draft.mainPosts.filter((v=> v.id !== action.data);
      break;
    case REMOVE_POST_FAILURE:
      draft.removePostLoading = false;
      draft.removePostError = action.error;
      break;
    case ADD_COMMENT_REQUEST:
      draft.addCommentLoading = true;
      draft.addCommentDone = false;
      draft.addCommentError = null;
      break;
    case ADD_COMMENT_SUCCESS: {
      const post = draft.mainPosts.find((v=> v.id === action.data.PostId);
      post.Comments.unshift(action.data);
      draft.addCommentLoading = false;
      draft.addCommentDone = true;
      break;
      // const postIndex = state.mainPosts.findIndex((v) => v.id === action.data.postId);
      // const post = { ...state.mainPosts[postIndex] };
      // post.Comments = [dummyComment(action.data.content), ...post.Comments];
      // const mainPosts = [...state.mainPosts];
      // mainPosts[postIndex] = post;
      // return {
      //   ...state,
      //   mainPosts,
      //   addCommentLoading: false,
      //   addCommentDone: true,
      // };
    }
    case ADD_COMMENT_FAILURE:
      draft.addCommentLoading = false;
      draft.addCommentError = action.error;
      break;
    default:
      break;
  }
});

export default reducer;

0

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

작성하신 코드 보여주세요.

0

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

빠른 답변 감사합니다!

Textarea.js 117번째 줄입니다.

0

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

Textarea.js 117번째 줄에 뭐가있나요?

lcm2822님의 프로필 이미지

작성한 질문수

질문하기