React 컴포넌트 조각 모음 🧩

Beanxx·2022년 10월 26일
0

React

목록 보기
1/5
post-thumbnail

온전히 내가 나중에 참고하려고 기록해두는 React 컴포넌트 조각들 🧩

✅ 데이터 추가 함수

// Example 1
const [expenses, setExpenses] = useState(DUMMY_EXPENSES);

const addExpenseHandler = (expense) => {
  setExpenses((prevExpenses) => {
	// [새로운 데이터, ...이전 데이터들] <- 새로운 데이터가 맨 위에 추가
    return [expense, ...prevExpenses]; 
  });
};

// Example 2
const [usersList, setUsersList] = useState([]);

const addUserHandler = (uName, uAge) => {
  setUsersList((prevUsersList) => {
	// [...이전 데이터들, 새로운 데이터] <- 새로운 데이터가 맨 아래에 추가
    return [
      ...prevUsersList,
      { name: uName, age: uAge, id: Math.random().toString() },
    ];
  });
};

✅ 자식 → 부모 컴포넌트로 값 전달

const saveExpenseDataHandler = (enteredExpenseData) => {
  const expenseData = {
    ...enteredExpenseData,
    id: Math.random().toString(), // 받아온 추가할 데이터에 id를 따로 추가
  };

  // 자식 → 부모 컴포넌트로 값 전달 로직
  // onAddExpense 함수는 부모 컴포넌트에 있던 데이터 추가 함수
  props.onAddExpense(expenseData);
};

✅ 객체 형태로 state 관리

const [userInput, setUserInput] = useState({
  enteredTitle: "",
  enteredAmount: "",
  enteredDate: "",
});

const titleChangeHandler = (event) => {
  // 이전 상태에 기반하는 상황에선 이 접근 방법 사용 권장!
  setUserInput((prevState) => {
    return { ...prevState, enteredTitle: event.target.value };
  });
};

✅ 재사용 가능한 레이아웃 컴포넌트

const Card = (props) => {
  // 이렇게 클래스를 props로 받아오면 기본 Card 컴포 css에 다르게 구현하고 싶은 css도 추가 가능
  const classes = "card " + props.className;
  return <div className={classes}>{props.children}</div>;
};

export default Card;
// 위의 컴포넌트 사용은 다른 컴포넌트에서 이런 식으로!

return (
  <div>
    <Card className="expenses">
	  ...
    </Card>
  </div>
);

✅ 변수에 JSX 문법 사용

// JSX 문법도 이렇게 변수에 할당해서 사용 가능!
let content = (
  <p style={{ textAlign: 'center' }}>No goals found. Maybe add one?</p>
);

if (courseGoals.length > 0) {
  content = (
    <CourseGoalList items={courseGoals} onDeleteItem={deleteItemHandler} />
  );
}

...

// 이렇게 구현하면 화면에 렌더링되는 부분 코드를 간결하게 줄일 수 있다!
return (
  <div>{content}</div>
)

✅ className

// class 이름에 '-'가 들어가면 적용이 안됨 (ex. styles.form-control)
// => 배열로 문자열을 감싸줘야 함 (styles['form-control'])

<div className={`${styles["form-control"]} ${!isValid && styles.invalid}`}/>

// style 내에 삼항 조건 연산자 사용할 때
<label style={{ color: !isValid ? "red" : "black" }}>Course Goal</label>

✅ .module.css (+ UI 컴포넌트 재사용하기)

// Button.js

// css 파일은 기존 css파일과 동일하게 구성하고, 파일 이름을 Button.module.css 이런 식으로 변경
import styles from "./Button.module.css";

// 재사용 가능한 버튼 컴포넌트~!
const Button = (props) => {
  return (
    <button type={props.type} className={styles.button} onClick={props.onClick}>
      {props.children}
    </button>
  );
};

export default Button;

✅ ref 사용해서 유효성 검사

// state로 유효성 검사를 하면 state 값이 바뀔 때마다 검사가 실행되는데
// 그게 아니라 폼 제출했을 때만 유효성 검사를 하고 싶을 땐 ref 사용하기!

// useRef의 값은 항상 객체이며, current props(그 ref가 연결된 실제 값)을 가짐
const nameInputRef = useRef();
const ageInputRef = useRef();

const [error, setError] = useState();

const addUserHandler = (event) => {
  event.preventDefault(); // 기본 동작 실행 방지

  const enteredName = nameInputRef.current.value;
  const enteredUserAge = ageInputRef.current.value;

  // trim(): 공백 제거
  if (enteredName.trim().length === 0 || enteredUserAge.trim().length === 0) {
    setError({
      title: "Invalid input",
      message: "Please enter a valid name and age (non-empty values)",
    });
    return;
  }

  if (+enteredUserAge < 1) {
    setError({
      title: "Invalid age",
      message: "Please enter a avalid age (> 0)",
    });
    return;
  }

  props.onAddUser(enteredName, enteredUserAge);

  // 값 초기화
  nameInputRef.current.value = "";
  ageInputRef.current.value = "";
};

return (
	<form onSubmit={addUserHandler}>
	  <label htmlFor="username">Username</label>
	  <input id="username" type="text" ref={nameInputRef} />
	  <Button type="submit">Add User</Button>
	</form>
);

✅ Context API로 전역 로그인 관리

// store/auth-context.js

const AuthContext = React.createContext({
  isLoggedIn: false,
  onLogout: () => {}, // TIP! 이렇게 설정해놓으면 다른 컴포넌트에서 사용할 때 키워드 자동완성됨!
  onLogin: (email, password) => {},
});

export const AuthContextProvider = (props) => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);

  useEffect(() => {
    const storedLogged = localStorage.getItem("isLoggedIn");

    if (storedLogged === "1") {
      setIsLoggedIn(true);
    }
  }, []);

  const logoutHandler = () => {
    localStorage.removeItem("isLoggedIn");
    setIsLoggedIn(false);
  };

  const loginHandler = () => {
    localStorage.setItem("isLoggedIn", "1");
    setIsLoggedIn(true);
  };

  return (
    <AuthContext.Provider
      value={{
        isLoggedIn: isLoggedIn,
        onLogout: logoutHandler,
        onLogin: loginHandler,
      }}
    >
      {props.children}
    </AuthContext.Provider>
  );
};

export default AuthContext;
// index.js

// <App/> 태그를 감싸줌으로써 전역으로 Context 사용 가능
<AuthContextProvider>
  <App />
</AuthContextProvider>
// 다른 컴포넌트에서 AuthContext 사용하기

import React, { useContext } from "react";
import AuthContext from "../../store/auth-context";

const Home = (props) => {
  const authCtx = useContext(AuthContext); // <- useContext로 가져다쓰면 편리!

  return (
    <Card>
      <Button onClick={authCtx.onLogout}>Logout</Button>
    </Card>
  );
};

export default Home;

✅ useEffect() 사용하여 로그인 유효성 검사

import React, { useState, useEffect } from "react";

const Login = (props) => {
  const [enteredEmail, setEnteredEmail] = useState("");
  const [emailIsValid, setEmailIsValid] = useState();
  const [enteredPassword, setEnteredPassword] = useState("");
  const [passwordIsValid, setPasswordIsValid] = useState();
  const [formIsValid, setFormIsValid] = useState(false);

  useEffect(() => {
    const identifier = setTimeout(() => {
      setFormIsValid(
        enteredEmail.includes("@") && enteredPassword.trim().length > 6
      );
    }, 500);

    // cleanup function: 모든 새로운 sideEffect()가 실행되기 전에, 그리고 컴포넌트가 제거되기 전에 실행됨
    // 첫번째 sideEffect()가 실행되기 전에는 실행되지 않음
    return () => {
      clearTimeout(identifier); // cleanup 함수가 실행되기 전에 설정된 타이머를 지움
    };
  }, [enteredEmail, enteredPassword]);

  const emailChangeHandler = (event) => {
    setEnteredEmail(event.target.value);
  };

  const passwordChangeHandler = (event) => {
    setEnteredPassword(event.target.value);
  };

  const validateEmailHandler = () => {
    setEmailIsValid(enteredEmail.includes("@"));
  };

  const validatePasswordHandler = () => {
    setPasswordIsValid(enteredPassword.trim().length > 6);
  };

  const submitHandler = (event) => {
    event.preventDefault();
    props.onLogin(enteredEmail, enteredPassword);
  };

  return (
    <Card>
      <form onSubmit={submitHandler}>
        <div>
          <label htmlFor="email">E-Mail</label>
          <input
            type="email"
            id="email"
            value={enteredEmail}
            onChange={emailChangeHandler}
            onBlur={validateEmailHandler}
          />
          {/* onBlur: 포커스 해제될 때의 이벤트 */}
        </div>
        <Button type="submit" disabled={!formIsValid}>Login</Button>
      </form>
    </Card>
  );
};

export default Login;

✅ useReducer() 사용하여 로그인 유효성 검사

// Login.js

// 리듀서 함수 내부에선 컴포넌트 함수에서 만들어진 어떤 데이터도 필요하지 않기 떄문에 컴포넌트 함수 범위 밖에서 만들 수 있음

// 이메일 리듀서
const emailReducer = (state, action) => {
  if (action.type === "USER_INPUT") {
    return { value: action.val, isValid: action.val.includes("@") };
  }
  if (action.type === "INPUT_BLUR") {
    return { value: state.value, isValid: state.value.includes("@") };
  }
  return { value: "", isValid: false };
};

// 비밀번호 리듀서
const passwordReducer = (state, action) => {
  if (action.type === "USER_INPUT") {
    return { value: action.val, isValid: action.val.trim().length > 6 };
  }

  if (action.type === "INPUT_BLUR") {
    return { value: state.value, isValid: state.value.trim().length > 6 };
  }

  return { value: "", isValid: false };
};
// Login.js

const Login = (props) => {
  const [formIsValid, setFormIsValid] = useState(false);

  const [emailState, dispatchEmail] = useReducer(emailReducer, {
    value: "",
    isValid: null,
  });

  const [passwordState, dispatchPassword] = useReducer(passwordReducer, {
    value: "",
    isValid: null,
  });

  const authCtx = useContext(AuthContext);

  const emailInputRef = useRef();
  const passwordInputRef = useRef();

  // 객체 구조분해할당
  const { isValid: emailIsValid } = emailState; // emailIsValid는 값이 아니라 별칭!
  const { isValid: passwordIsVlid } = passwordState;

  // 위의 구조분해할당으로 isValid만 가져다가 아래 useEffect 내에 사용하므로 값은 변경되어도 유효성이 변경되지 않으면 useEffect가 재실행되지 않음!
  useEffect(() => {
    const identifier = setTimeout(() => {
      setFormIsValid(emailIsValid && passwordIsVlid);
    }, 500);

    return () => {
      clearTimeout(identifier);
    };
  }, [emailIsValid, passwordIsVlid]);

  const emailChangeHandler = (event) => {
    dispatchEmail({ type: "USER_INPUT", val: event.target.value });
  };

  const passwordChangeHandler = (event) => {
    dispatchPassword({ type: "USER_INPUT", val: event.target.value });
  };

  const validateEmailHandler = () => {
    dispatchEmail({ type: "INPUT_BLUR" });
  };

  const validatePasswordHandler = () => {
    dispatchPassword({ type: "INPUT_BLUR" });
  };

  const submitHandler = (event) => {
    event.preventDefault();

    if (formIsValid) {
      authCtx.onLogin(emailState.value, passwordState.value);
    } else if (!emailIsValid) {
      emailInputRef.current.focus();
    } else {
      passwordInputRef.current.focus();
    }
  };

  return (
    <Card>
      <form onSubmit={submitHandler}>
        <Input
          ref={emailInputRef}
          id="email"
          label="E-Mail"
          type="email"
          value={emailState.value}
          isValid={emailIsValid}
          onChange={emailChangeHandler}
          onBlur={validateEmailHandler}
        />
        <Button type="submit" className={classes.btn}>Login</Button>
      </form>
    </Card>
  );
};

export default Login;
profile
FE developer

0개의 댓글