728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다.

 

useHistory

저번에는 history를 사용하기 위해 컴포넌트를 withRouter로 감싸주고 history 객체에 접근했지만, useHistory를 사용해서도 history 객체에 접근할 수 있다. 

 

import { useHistory } from 'react-router-dom'; 

const Main = () => {
    const history = useHistory(); 

    return (
        <Center>
            <Button onClick={() => history.push('/chat')} style={{ marginBottom: '2rem' }}>잡담해요</Button>
            <Button onClick={() => history.push('/need')} style={{ marginBottom: '2rem' }}>필요해요</Button>
            <Button onClick={() => history.push('/chatwithdev')}>개발자와 소통해요</Button>
        </Center>
    );
};

export default Main;

 

728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

 history.push()와 history.replace()의 차이점

라우팅 변경을 위해 가장 빈번히 사용되는 메소드 중 history.push()history.replac()의 차이점은 무엇일까? 

 

Home > A > B > C 순으로 페이지 이동을 할 때,

 

B에서 history.push()를 사용하면 

 

import React from 'react';

function B({ history }) {
  history.push('/C');
  return <div>B</div>;
}

export default B;

 

Home > A > B > C 순으로 history에 쌓여, 마지막 페이지에서 뒤로가기 버튼을 누르면 B 페이지로 되돌아간다. 

 

똑같이 Home > A > B > C 순으로 페이지 이동을 하는데, 

 

B에서 history.replace()를 사용하면 

 

import React from 'react';

function B({ history }) {
  history.replace('/C');
  return <div>B</div>;
}

export default B;

 

Home > A > C 순으로 history에 쌓여, 마지막 페이지에서 뒤로 가기 버튼을 누르면, A 페이지로 되돌아간다. 

 

즉, history를 스택이라고 생각했을 때, push는 쌓여 있는 history 위에 쌓는 것이고, replace쌓여 있는 history 제일 위의 원소와 현재 넣는 원소를 말 그대로, replace(대체)하는 것이다. 

728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

withRouter는 언제 사용할까?

 

커뮤니티 게시판에서 회원가입 폼에 정보를 입력하고, 회원가입하기 버튼을 눌렀다면 메인 페이지로 넘어가게 된다. 이렇게 리액트에서 페이지를 이동할 수 있는 것은 'react-router-dom'을 이용해 페이지의 기록을 알 수 있기 때문이다. 

 

Router컴포넌트의 path와 라우팅할 컴포넌트를 정해줄 수 있는데, 해당하는 Router는 props를 통해 history 객체를 전달 받게 된다.

 

<Router>
            <Switch>
                <Route exact path="/" component={Auth(LogOutMain, false)} />
                <Route exact path="/login" component={Auth(LogInPage, false)} />
                <Route exact path="/signup" component={Auth(SignUpPage, false)} />
                <Route exact path="/main" component={Auth(Main, true)} />
                <Route path="/*">404 Not Found</Route>
            </Switch>
</Router>

 

history 객체goBack(), goFoward() 등 앞/뒤로 이동할 수 있는 메소드 등 다양한 메소드와 관련 객체가 있다. 

 

그 중 라우팅 변경을 위해 가장 빈번히 사용되는 메소드가 바로 push이다. 아래의 코드처럼 이동하고자 하는 path를 history.push 안에 넣어주면, 원하는 경로(컴포넌트)로 이동 가능하다. 

 

history.push('/main');

 

그래서 나도 '회원가입하기' 버튼을 눌렀을 때 메인페이지로 이동하게 하고자 history.push를 사용했으나 꿈쩍도 하지 않는 것이다. 

 

const onSubmitForm = useCallback(() => {
        dispatch(registerUser({ role, phone, nickname }));
        props.history.push('/main');
}, [role, phone, nickname]);

 

 

이유를 찾아보니, '라우터가 아닌' 컴포넌트에서 history(나 location, match)와 같은 라우터에서 사용하는 객체를 쓰려면 withRouter라는 HOC을 사용해야 한다. 'react-router-dom'에서 'withRouter'를 import하고, export 하는 컴포넌트를 withRouter로 감싸주면 된다. HOC은 어제도 배웠지만, 컴포넌트를 인자로 받아서 새로운 컴포넌트를 리턴하는 함수이다. 

 

import { withRouter } from 'react-router-dom';

const SignUpPage = (props) => {
	// ,,,,

    const onSubmitForm = useCallback(() => {
        dispatch(registerUser({ role, phone, nickname }));
        props.history.push('/main');
    }, [role, phone, nickname]);

    return (
        <Center>
            <form onSubmit={onSubmitForm} aria-label="회원가입 폼입니다.">
                // ,,,,,
                <Button type="submit">회원가입하기</Button>
            </form>
        </Center>
    );
};

export default withRouter(SignUpPage);
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

java.sql.SQLNonTransientConnectionException : Public Key Retrieval is not allowed 에러 해결

MySql이 8.x 이상인 경우 발생하는 에러로, jdbc urlallowPublicKeyRetrieval=true 설정을 추가해주어야 한다고 한다. 

 

그런데 나의 경우는, 저 설정 뿐만 아니라(기존에 설정되어 있던 'useSSL=false' 외에) 'useUnicode=true'와 'characterEncoding=UTF-8'도 추가로 설정해주니 에러가 해결되었다. 

 

spring.datasource.url=jdbc:mysql://localhost:3306/db-name?useUnicode=true&characterEncoding=UTF-8&allowPublicKeyRetrieval=true&useSSL=false

 

Auth(HOC)

회원가입과 로그인 기능을 제공하는 리액트 사이트를 만들고 있다. 그렇다보니 누구나 진입이 가능한 페이지도 있고, 로그인한 회원만 진입이 가능한 페이지, 로그인한 회원은 진입하지 못하는 페이지가 있다. 이때, 인증을 위해 HOC, High Order Component function을 사용한다. 

 

High Order Component function는 컴포넌트를 인자로 받아서 새로운 컴포넌트를 리턴하는 함수이다. 서버로부터 응답 받은 유저의 상태(로그인/로그아웃/관리자)에 따라 Auth function에 인자의 값을 다르게 줌으로써 페이지의 인증을 컨트롤할 수 있다.

 

src/hoc/auth.js에 다음과 같은 파일을 만든다. 

 

import { useEffect } from 'react';
import { useSelector } from 'react-redux';

export default (Component, option, adminRoute = null) => {
    /*
    option: null => 누구나 출입가능한 페이지
          : true => 로그인한 유저만 출입가능한 페이지 
          : false => 로그인한 유저는 출입 불가능한 페이지 
    */

    const AuthenticateCheck = (props) => {
        const isLoggedIn = useSelector((state) => state.user.isLoggedIn);

        useEffect(() => {
            // 로그인 되어 있지 않다면, logOutMain으로 이동시키기
            if (!isLoggedIn && option) {
                props.history.push('/');
            }
        }, []);

        return <Component />;
    };

    return AuthenticateCheck;
};

 

 

이제 인증이 필요한 페이지에 다음과 같이 적용해준다. 두 번째 인자가 option이고, option이 null이면 누구나 출입 가능한 페이지, true로그인한 사용자만 출입 가능한 페이지, false로그인한 사용자는 출입할 수 없는 페이지가 된다. 

 

import { useState } from 'react';
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';

import Auth from './hoc/auth';
import LogOutMain from './components/LogOutMain';
import Main from './components/Main';
import LogInPage from './components/LogInPage';
import SignUpPage from './components/SignUpPage';

const App = () => {
    return (
        <Router>
            <Switch>
                <Route exact path="/" component={Auth(LogOutMain, false)} />
                <Route exact path="/login" component={Auth(LogInPage, false)} />
                <Route exact path="/signup" component={Auth(SignUpPage, false)} />
                <Route exact path="/main" component={Auth(Main, true)} />
                <Route path="/*">404 Not Found</Route>
            </Switch>
        </Router>
    );
};

export default App;
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

JavaScript 배열에서 max 값 찾기(spread operator)

JavaScript 배열에서 max 값을 찾기 위해서는 spread operator(전개 연산자)를 사용해야 한다.

 

배열 num의 max 값을 찾기 위해 아래 코드처럼 그냥 바로 Math.max(num) 하면 안된다는 것이다. 

 

const num = [1, 3, 5, 7, 9]; 

console.log(Math.max(num)); 
// 결과: Nan

 

Math.max()입력된 숫자 중 가장 큰 수를 반환한다. 배열에서 가장 큰 값을 찾기 위해 배열을 Math.max의 매개변수로 바로 넣어버리면 배열을 숫자로 인식하지 않기 때문에 Nan을 반환하는 것이다. 

 

즉, Math.max([1, 3, 5, 7, 9])의 형태가 아닌 Math.max(1, 3, 5, 7, 9) 형태로 들어가야 Max를 제대로 반환하게 된다. 

 

[1, 3, 5, 7, 9]1, 3, 5, 7, 9로 바꾸어 줄 수 있는 것이 spread operator(...), 전개 연산자이다. 

 

MDN에서는 spread operator를 다음과 같이 설명하고 있다. 

 

> 전개 구문을 사용하면 배열이나 문자열과 같이 반복 가능한 문자를 0개 이상의 인수 (함수로 호출할 경우) 또는 요소 (배열 리터럴의 경우)로 확장하여, 0개 이상의 키-값의 쌍으로 객체로 확장시킬 수 있습니다.

 

즉, 배열을 요소로 바꾸어주는 것이라고 생각하면 된다. 

 

그래서 배열에서 max 값을 찾고자 한다면, 아래처럼 배열 이름 앞에 spread operator를 넣어주면 된다. 

 

const num = [1, 3, 5, 7, 9]; 

console.log(Math.max(...num)); 
// 결과: 9

 

배열이 특정 요소를 포함하고 있는지 판별(Array.includes())

특정 요소가 몇 번째 인덱스에 있는지 확인하고 싶을 때는 Array.indexOf(),

특정 요소를 배열이 포함하고 있는지 확인하고 싶을 때는 Array.includes() 

 

for ... in, for ... of 

for ... in은 객체 key 순환, 

for ... of는 객체 value 순환

 

서버에서 로그인 인증하는 법 

프론트에서 id와 password를 보내면, 백에서는 받아온 id를 먼저 찾고, 이 id가 가지고 있는 password와 받아온 password가 같은지 확인한다. 

 

dispatch가 먼저일까, 서버와의 통신이 먼저일까

action을 dispatch하는 게 먼저일지 서버로 데이터를 보내는 게 먼저일지를 고민했다. 

 

그런데 아래처럼, axios post한 결과가 succeed냐, failed냐에 따라 dispatch 해야하는 action이 달라진다면, 서버와 먼저 통신하고, action을 dispatch하는 게 맞을 것이다. 

 

export function loginRequest(username, password) {
  return (dispatch) => {
      dispatch(login());
 
      // API REQUEST
      return axios.post('/api/account/signin', { username, password })
      .then((response) => {
          // SUCCEED
          dispatch(loginSuccess(username));
      }).catch((error) => {
          // FAILED
          dispatch(loginFailure());
      });
  };
}
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

react redux axios post 

회원가입 폼 제출 시, 해당 user가 넘긴 정보(nickname, phone, role)가 dispatch되고 axios post를 하는 코드를 작성했다.

 

여기서 잠깐 redux 개념을 정리하고 가자면,,, 

redux데이터의 중앙 저장소이다. 각 컴포넌트에서 필요로 할때 꺼내 쓸 수 있는 장소라고 생각하면 된다. 

 

redux에서 data를 바꾸기 위해서action이 필요하고, action을 dispatch하면 중앙 저장소의 data가 변경된다. 그런데 action을 dispatch한다고 data가 자동으로 바뀌는 것은 아니고, reducer에서 어떻게 바꿀지를 구현해주어야 한다.

 

회원가입 폼을 제출하면, registerUser 액션을 dispatch 중앙저장소에 user data를 추가해준다.

 

// components > SignUpPage.js

const onSubmitForm = useCallback(
        async () => {
            const nickname = await makeNickname();
            await dispatch(registerUser({ role, phone, nickname }));
        },
        [role, phone],
);

 

registerUser 액션은 registerAction을 dispatch하고, axios post를 통해 해당 서버로 data를 넘겨준다.

 

// reducers > user.js

export const registerUser = async (data) => {
    return (dispatch) => {
        dispatch(registerAction(data));
        return axios.post('http://localhost:8080/users', data);
    };
};

export const registerAction = (data) => {
    return {
        type: REGISTER_USER,
        data,
    };
};

 

JavaScript insertion sort(삽입 정렬)

삽입 정렬은 array의 두번째 인덱스에서 시작해서 마지막 인덱스가 될때까지, 앞의 요소들과 비교해 작으면 삽입하는 정렬 방식이다. 

 

function solution(arr){
  const answer = arr;

  for(let i = 1; i < arr.length;  i++){
  	
    // tmp는 비교대상이 될 임시적 변수 
    const tmp = arr[i]; 
    
    // j는 tmp의 앞 인덱스
    let j = i - 1
    
    for(; j >= 0; j--){
      if(tmp < arr[j]){
        arr[j+1] = arr[j]; 
      } 
      
      else{
        // tmp가 더 크면 더 이상 비교할 필요 없다. 앞의 요소들은 이미 정렬된 상태이므로
        break; 
      }
    }
    
    // j가 -1이 된 경우, 즉 모든 요소가 tmp보다 컸다면 arr[0]에 tmp에 들어가야 하고
    /*
    	중간에 break된 경우, 
        예를 들면 j가 2일때 break 되었다면, 
        arr[2]보다는 tmp가 크고, arr[4]보다는 tmp가 작다는 뜻이므로
        j가 3인 자리에 tmp가 들어가야 한다. 
    */
    // 즉, 둘 중 어떤 경우라도 항상 인덱스가 (j + 1)인 자리에 tmp가 들어가야 한다.
    arr[j+1] = tmp; 
  }

  return answer; 
}

let arr = [11, 7, 5, 6, 10, 9]; 
console.log(solution(arr));

 

JavaScript Array.slice()

slice()는 배열에서 특정 인덱스부터 특정 인덱스까지의 복사가 필요할 때 주로 쓰인다. 즉, 원본 배열은 건드리지 않는 메소드이다. 

 

arr.slice(begin, end) 형태로 쓰이며, 'begin'번째 인덱스부터 'end - 1'번째 인덱스까지 복사된다. 즉, end 인덱스는 미포함이다.    

 

const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];

console.log(animals.slice(2, 4));
// 결과: ["camel", "duck"]
// 2번째 인덱스부터 (4-1)번째 인덱스까지 복사, 즉, 4번째 인덱스는 미포함

// 배열 전체를 복사하고 싶을 때
console.log(animals.slice());

console.log(animals.slice(2));
// 결과: ["camel", "duck", "elephant"]
// 2번째 인덱스부터 끝 인덱스까지 복사됨

 

 

728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

 

React에서 Form을 사용해서 구현할 때 다양한 input을 받아야 하는 경우, 관리할 state가 많아지면서 코드가 쉽게 복잡해질 수 있다. 

 

또, React에서는 Form 양식을 제출할 때, 성가신 것들이 몇 가지 있다. 

 

  • Form 상태에서 값 가져오기
  • 유효성 검사 및 오류 메세지
  • Form submit 핸들링 

Formik라는 라이브러리를 사용하면 위의 세 가지 요소를 한 곳에 배치할 수 있어 편리하다고 한다. Formik는 Provider를 통해 상태와 메소드를 ui에 공유한다. 이에 따라 form 내에 있는 모든 컴포넌트들이 동일한 상태를 공유할 수 있게 된다.  

 

위와 같은 장점들이 있어, 이번 프로젝트에서 Formik를 사용해볼까 했었는데, 내가 하는 프로젝트는 '시각 장애인들을 위한 커뮤니티'이다. 회원가입 시 번거로움을 덜어 드리고 싶어서, 전화번호만 입력을 받을 예정이라 그리 많은 input이 필요하지는 않아 그냥 React의 Form을 사용하기로 했다. 

 

오늘은 만약 사용자가 중복된 전화번호를 입력했을 경우, 폼이 submit되지 않고 경고창이 뜨고, 전화번호 input이 reset되는 것을 구현했다.

 

 

이 때, 만약 중복된 전화번호를 입력했다면 '회원가입하기'를 눌렀을 때 아래와 같이 경고창이 뜨고

 

 

전화번호 input창이 reset되게 말이다. 그대로 둘 수도 있겠지만, 그러면 시각장애인들이 다시 input을 지워야 하니 번거로울 것 같아서 그냥 아예 reset을 했다. 

 

 

중복확인을 하더라도 submit되어 data가 넘어가면 어쩌지 했는데, 만약 registered된 사용자라면, 'alert'문을 return하면 경고창만 띄워지고, data가 submit되지는 않았다. 

 

const onSubmitForm = useCallback(
        async (e) => {

            // 전화번호 중복 확인
            // if(){ //중복이라면
            //     setRegistered(true);
            // }

            // 중복이라면 경고 메세지 띄우고,
            if (registered) {
                setPhone('');
                return alert('이미 등록된 사용자입니다. 전화번호를 다시 입력해주십시오.');
            }
            const nickname = await makeNickname();
            await dispatch(registerAction({ role, phone, nickname }));
        },
        [role, phone],
);
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

 

JavaScript 배열에서 랜덤 값 추출하려면, 배열의 인덱스 값을 랜덤으로 뽑아주면 된다. 

 

예를 들어, 길이가 10인 배열에서 랜덤 값을 뽑아주려면, 0~9 중에서 하나의 인덱스가 뽑히면 된다. 

 

Math.random()은 0부터 1미만의 수를 랜덤하게 리턴하므로 범위가 0~9가 되려면 Math.random() * 10, 즉, Math.random() * array.length 한 결과의 소수점을 버림해주면 된다. 

 

const num = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 
const randomVal = num[Math.floor(Math.random() * num.length))]; 

 

인덱스는 정수이다. 그래서 (Math.random() * 10의 결과로 9.xx와 같이 소수점이 붙은 형태로 리턴될 수 있기 때문에) 소수점을 버려줘야 한다. 

 

0 <= Math.random() < 1
0 <= Math.random() * 10 < 10
728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다. 

 

스프링부트 프로젝트를 실행하려는데 'java.lang.unsupportedclassversionerror' 에러가 발생했다. 

 

프로젝트를 11버전으로 실행해야 하는데, 8버전으로 실행하려 하다보니 발생한 에러이다. 

 

나는 8버전과 11버전이 모두 설치되어 있었는데 환경변수에서 JAVA_HOME의 경로가 8버전으로 연결되어 있었다. 

 

이 경우에는 JAVA_HOME의 경로만 11버전으로 연결해주면 된다. 

 

우선, 제어판 > 시스템 환경 변수 편집에 들어가서 '환경변수'를 클릭한다. 

 

 

그리고 시스템 변수의 JAVA_HOME을 더블 클릭한다. 

 

 

변수 값에 버전 11이 설치되어 있는 bin 디렉토리 경로를 넣어준다. 

 

728x90
728x90

*하루동안 새롭게 알게된 부분, 에러를 해결한 방법 등을 작성하는 개발일기입니다. 다른 사람에게도 설명해줄 수 있도록 제 머릿속에 넣기 위해 정리를 시작하게 되었습니다.   

 

React + Spring Boot로 프로젝트를 하고 있는데, CORS 에러가 발생했다. 

 

CORS를 이해하기 위해서는 SOP를 알아야 한다. 

 

SOP(Same Origin Policy), 말그대로 같은 Origin에만 요청을 보낼 수 있게 제한하는 보안정책이다. Origin은 다음 세 가지로 구성되어 있다. 

  • URI Schema ex) http, https
  • Hostname ex) localhost, naver.com
  • Port ex)8080

세 가지 중 하나라도 구성이 다르면, SOP 정책에 걸리기 때문에 ajax 요청을 보낼 수 없다. 

 

그래서 CORS가 필요해졌다. 즉, 다른 Origin이라도 서로 요청을 주고 받을 수 있게 말이다. 

728x90

+ Recent posts