티스토리 뷰

select 태그 없이 dropDown 구현

function 안 함수들

 

  const navigate = useNavigate();
  const menu = ['전략배분 (정적자산배분)', '듀얼모멘텀', 'VAA', 'DAA'];
  const [selectedMenu, setSelectedMenu] = useState('전략배분 (정적자산배분)');
  const [activeDropdown, setActiveDropdown] = useState(false);

  const onClickOption = useCallback(() => {
    setActiveDropdown((prev) => !prev);
  }, []);

  const onClickSelect = useCallback(
    (item) => {
      setSelectedMenu(item);
      switch (item) {
        case '듀얼모멘텀':
          navigate('/dual_momentum');
          break;
        case 'VAA':
          navigate('/vaa');
          break;
        case 'DAA':
          navigate('/daa');
          break;
        default:
          navigate('/');
      }
    },
    [navigate],
  );

element

                <DropdownBox
                  onClick={onClickOption}
                  activeDropdown={activeDropdown}
                >
                  <Label>{selectedMenu}</Label>
                  <SelectOptions activeDropdown={activeDropdown}>
                    {menu.map((item) => (
                      <Option key={item} onClick={() => onClickSelect(item)}>
                        {item}
                      </Option>
                    ))}
                  </SelectOptions>
                </DropdownBox>

css

const DropdownBox = styled.div`
  position: relative;
  width: 640px;
  height: 46px;
  background: rgb(14, 14, 14);
  border: 1px solid rgb(159, 159, 159);
  border-radius: 6px;
  padding-top: 12px;
  cursor: pointer;
  text-align: center;

  &::after {
    content: url(${arrow});
    position: absolute;
    top: 14px;
    right: 27px;
    width: 14px;
    transform: ${(props) =>
      props.activeDropdown ? `rotate(180deg)` : `unset`};
  }
`;

const Label = styled.label`
  color: rgb(252, 252, 252);
  font-weight: 500;
  font-size: 18px;
  text-align: center;
`;

const SelectOptions = styled.ul`
  display: ${(props) => (props.activeDropdown ? `block` : `none`)};
  position: absolute;
  top: 50px;
  left: 0;
  width: 640px;
  overflow: hidden;
  z-index: 2;
  background: rgb(14, 14, 14);
  border: 1px solid rgb(62, 62, 62);
  border-radius: 6px;
`;

const Option = styled.li`
  width: 640px;
  padding: 12px 0 12px 0;
  text-align: center;
  font-weight: 500;
  font-size: 18px;
  color: rgb(252, 252, 252);
  transition: background-color 0.2s ease-in;
  &:hover {
    background: rgba(236, 97, 38, 0.3);
  }
`;

 

[ERROR] Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`

 

'onChange' 이벤트 핸들러 없이 form 내부에서 'value' prop를 사용하였습니다. value를 사용한 Element는 읽기 전용으로 렌더링 될 것입니다. 만약 값을 수정하고 싶다면, defaultValue를 사용해야 합니다. 그렇지 않으면, 'onChage' 또는 'readOnly'로 설정해야 합니다. 

대용량 처리

※ 모든 자산군 데이터를 스토어에 저장하고 싶다면, 다음과 같은 방법을 사용할 수 있습니다.

 

1. 자산군 데이터를 미리 로드하여 JSON 파일로 저장합니다.

2. JSON 파일을 읽어와 리덕스 스토어에 저장합니다.

 

이렇게 하면, 자산군 데이터가 10만개라도 한 번만 불러오면 되므로 성능적으로 더 효율적입니다. 하지만, 자산군 데이터가 변경되는 경우에는 새로운 JSON 파일을 생성하여 업데이트해야 하므로 유지보수에 대한 고려가 필요합니다.

 

+ React.memo() 사용: 만약 컴포넌트가 불필요하게 재랜더링 되는 경우, 성능이 저하될 수 있습니다. 이런 경우 React.memo()를 사용하여 컴포넌트를 최적화할 수 있습니다. React.memo()는 컴포넌트의 props가 변경될 때만 컴포넌트를 다시 렌더링합니다.

+ 최적화된 CSS 적용: 컴포넌트를 최적화하는 것 외에도, CSS를 최적화하여 성능을 향상시킬 수 있습니다. 예를 들어, 컴포넌트의 크기를 고정하고, 필요한 데이터만 가져와서 보여주는 것

 

data.json

{
  "assets": [
    "Agilent Technologies (A-US)",
    "동화약품",
    "KR모터스",
    "경방",
    "메리츠화재",
    "삼양홀딩스",
    "화이트진로",
    "유한양행",
    "CJ대한통운",
    "하이트진로홀딩스",
    "두산",
    "성창기업지주",
    "DL",
    "유유제약",
    "일동홀딩스",
    "삼성전자",
    "SK하이닉스",
    "우리기술",
    "판타지오",
    "삼성생명"
  ]
}

assetSlice.js

import { createSlice } from '@reduxjs/toolkit';
import data from '../data/data.json';

const initialState = {
  assets: data.assets,
  isLoading: false,
  error: null,
};

const assetSlice = createSlice({
  name: 'assets',
  initialState,
  reducers: {},
  extraReducers: {},
});

export default assetSlice.reducer;

이렇게 가져온다.

assets: data.assets

api 호출 없이 더미데이터만 보여줘서 reducers와 extraReducers는 필요없었다.

chatGPT 한테 리펙토링시키면 이렇게 알려준다.

  1. assets 상태 객체를 직접 변경하지 말고, 불변성을 유지하며 업데이트하는 것이 좋습니다. 이를 위해 immer 라이브러리를 사용할 수 있습니다.
  2. 현재 reducers와 extraReducers 필드가 모두 빈 객체이므로, 이들을 삭제하고 나중에 필요한 경우에 추가하는 것이 좋습니다.

produce 함수는 immer 라이브러리를 사용하여 assets 상태 객체를 불변성을 유지하며 업데이트하는 데 사용됩니다. 이제 assets 상태 객체를 업데이트할 때 다음과 같이 produce 함수를 사용할 수 있습니다.

import produce from 'immer';

...

const assetSlice = createSlice({
  name: 'assets',
  initialState,
  reducers: {
    setAssets: (state, action) => {
      return produce(state, draftState => {
        draftState.assets = action.payload;
      });
    },
    setLoading: (state, action) => {
      return produce(state, draftState => {
        draftState.isLoading = action.payload;
      });
    },
    setError: (state, action) => {
      return produce(state, draftState => {
        draftState.error = action.payload;
      });
    },
  },
  extraReducers: {
    // 필요한 경우 추가
  },
});

음 내 일자리 괜찮은 걸까?

ProgressBar

로딩 중 스피너 만들기
https://loading.io/

이 사이트에서 매우 쉽게 스피너를 만들 수 있다!

  // 선언문과 함수들
  const [isProgress, setIsProgress] = useState(false);
  const [width, setWidth] = useState(20);
  const [seconds, setSeconds] = useState(10);

    useEffect(() => {
        let intervalId;
        if (width < 210) {
          intervalId = setInterval(() => {
            setWidth((prevWidth) => prevWidth + (190 / (seconds * 1000)) * 5);
          }, 5);
        }

        return () => {
          clearInterval(intervalId);
        };
      }, [width, seconds]);
  
  // return문
  {isProgress && width < 210 ? (
                      <ProgressBar width={width} />
                    ) : (
                      <TextWrap>
                        <BackTestText isClickBacktest={isClickBacktest}>
                          {isClickBacktest ? '생성중...' : '백테스트'}
                        </BackTestText>
                        {isClickBacktest && (
                          <Spinner>
                            <img
                              src={spinner}
                              alt="로딩중"
                              width="unset"
                              height="unset"
                            />
                          </Spinner>
                        )}
                      </TextWrap>
                    )}
                    

	// css
    const ProgressBar = styled.div`
      border-radius: 6px;
      background: linear-gradient(to right, rgb(236, 97, 38), rgb(236, 38, 38));
      height: 53px;
      width: ${(props) => props.width}px;
    `;

10초 동안 width가 20px 에서 210px로 증가한다.

최대 width가 됐을 때 ProgressBar를 없어지게 하는 것까지 구현했다.

 

https://developer-talk.tistory.com/108

 

[JavaSciprt]Warning : You provided a `value` prop to a form field without an `onChange` handler.

경고 『 Warning: You provided a 'value' prop to a form field without an 'onChange' handler. This will render a read-only field. If the field should be mutable use 'defaultValue'. Otherwise, set either 'onChange' or 'readOnly' 』 해석 'onChange' 이

developer-talk.tistory.com

https://anerim.tistory.com/221

 

[리액트 React] 리액트 로딩화면(스피너) 추가하기 / api 호출 시 로딩화면(스피너) 넣기

안녕하세요. 디자인도 하고, 개발도 하는 '디발자 뚝딱'입니다. 이번 포스팅에서는 리액트에서 api 호출했을 때 로딩화면/스피너 넣는 방법에 대해 공유하겠습니다. 어렵지 않으니 차례대로 따라

anerim.tistory.com