728x90

 

Nextjs로 

평소와 같은 작업을 하고 머지 후 빌드를 돌렸다.

그런데 로컬에서 나지 않던 문제가 갑자기 발생했다.

 

"Expected positive integer for height but received 0 of type number"

 

로컬에서는 잘만 나왔는데 갑자기 빌드에서 안된다라?  이 사실 자체가 두근거리게 한다 ^^; 

yarn run dev로 돌릴때는 문제 없었는데 로컬에서 빌드를 돌려도 위와같은 에러가 똑같이 발생했다.

 

에러문구는 새롭게 추가된 특정 이미지와 sharp 관련된 부분을 에러로 찍어내고 있었다.

뭔가 높이가 0이라고 하는것도 같은데...

 

 

이슈 : https://github.com/vercel/next.js/issues/40602

 

Error in next/image with high aspect ratio images · Issue #40602 · vercel/next.js

Verify canary release I verified that the issue exists in the latest Next.js canary release Provide environment information Operating System: Platform: darwin Arch: arm64 Version: Darwin Kernel Ver...

github.com

 

 

원인

next js의 next-image-loader.js 의 에러 때문이었다.

특정 값(blurHeight) 계산할때 미세한 높이가 0으로 반올림이되는데,

이에 해당 이미지를 image/next 의 Image 태그로 사용하면 에러가 발생하게 됬던것이다.

(문제가된 나의 이미지 파일도 가로 길이는 400px인 반면에 높이는 9px도 되지 않았고, next의 Image 태그를 사용했다)

 

해결

현재 Next 특정 버전에서 문제가 발생하여 새로운 버전들에서는 문제가 되지 않는것 처럼 보인다.

다만 버전을 올릴수 없거나 하는 상황이면 어떻게 해야할까? (회사에선 거의 그러하다)

next-image-loader.js는 nextjs의 내부 파일이기 때문에 수정해서 관리하기는 껄끄럽다.

 

이럴땐,

넓이에 비해 너무 작은 이미지 사용을 피하거나, Image 태그 대신에 next-image-loader를 사용하지 않는 html 기본 img 태그를 사용하는것이다.

 

후자의 방법으로 수정하고나니 빌드에러에서 해방될 수 있었다!

디자이너님 이제 너무 얇고 긴 이미지는 안돼요!!!!!!!!!!!

 

 

 

728x90
728x90

 

Nextjs에서 사용되는 redirect는 서버에서 페이지 이동을 시켜줄때 매우 듬직한 역할을 한다.

클라이언트 단에서는 useRouter를 이용해서 쉽게 이동 가능하지만 훅이다 보니 서버단에서는 해당 방식을 사용하기 힘들다. 그래서 redirect 함수를 이용해서 특정 상황에 다른 페이지로 보내줄 수 있다. 그런데 이 redirect의 위치에 따라서 작동하지 않을 수 있다는 사실을 알고 있는가? 나는 이번에 처음알았다.

 

Nextjs 14버전 서버사이드 쪽에서 api를 호출하고 그 값에 따라서 redirect를 사용하는 함수를 간단하게 만들었다. 값이 있으면 해당 redirect를 사용한다. 아래 함수는 원하는대로 작동할 수 있을까?

try{
  const res = await getName();
  if(res){
    redirect('/name')
  }
}catch(e){
  // 에러 처리
  console.log(e)
}

 

위 함수는 아쉽게도 res 값이 있더라고 원하는 방향으로 작동하지 않는다. 단 redirect는 실행되었다. 

음? 페이지 전환도 안됐는데 redirect는 실행이 되었다는게 무슨 소리인가? 서버 쪽 콘솔을 확인하면 그 답을 알 수 있다.

 

Error: NEXT_REDIRECT 라는 에러가 찍히는 것을 알 수 있다. redirect가 작동하는 키는 에러를 발생시키는 것에 있다. 에러를 발생시키면서 현재 서버쪽에서 작동하고 있는 플로우를 멈추고 따로 지정한 페이지로 이동하게끔 하는것이다. 때문에 에러를 발생시키는 redirect를 try~catch 문에 넣으면 redirect가 발생시킨 에러를 catch가 잡아버리기 현상이 발생하게 되고, 원하는 페이지 이동이 발생하지 않게되는 것이다. 따라서 redirect 함수는 try~catch문 안에 사용하는 것을 삼가해야 할 것!!

 

https://nextjs.org/docs/app/api-reference/functions/redirect

 

Functions: redirect | Next.js

API Reference for the redirect function.

nextjs.org

 

 

 

이번 기회에 찾아보다보니... 친절하게 공식문서에 써..있다ㅎ;;

  • In Server Actions and Route Handlers, redirect should be called after the try/catch block.

옵션을 통해서 replace 와 push를 고를 수 있다는 점까지 챙겨가면서.. 이 참에 위 부분 공식문서 살짝 정독해보는 시간을 가져보는 것도 좋겠다!

 

 

결론 : try catch 안에서 redirect 사용하지 않기!

 

728x90
728x90

위와 같은 에러가 발생했다. Warning: Extra attributes from the server: cz-shortcut-listen

 

이는 크롬 확장자 중 colorZilla 때문에 발생한다고 한다. colorzilla여서 cz?

위 확장자는 화면에서 원하는 색을 따올 수 있게 도와주는 확장자이다.

SSR환경에서 발생하는데

서버에서 만들어진 HTML에 브라우저 확장자가 추가 속성을 넣게 되면서
잉 서버랑 클라이언트 브라우저가 다른데? 하는 상황이다. colorzilla 외에도 간혹 이런 해프닝을 일으키는 확장자들이 있다고 한다.

 

해결방법은 해당 확장자를 비-활성화 해버리면 일단 깔끔하게 해결가능하다.

 

그럼 colorzilla.. 잘쓰고 있었다면 

나의 경우 크롬 eyedropper 를 쓰고있는데 추천할 만한것 같다!

 

https://chromewebstore.google.com/detail/eye-dropper/hmdcmlfkchdmnmnmheododdhjedfccka

 

Eye Dropper - Chrome 웹 스토어

Eye Dropper allows you to easily pick a color from any web page or an advanced color picker. It's great tool for web developers and…

chromewebstore.google.com

 

 

 

728x90
728x90

 

 

서론

브랜치를 새롭게 만들고나서 첫 git push를 하면 항상 같은 에러가 우리를 반겨준다. (또 너야?)

fatal: The current branch feature/order-agent has no upstream branch.
To push the current branch and set the remote as upstream, use

    git push --set-upstream origin {새로만든 브랜치 이름}

To have this happen automatically for branches without a tracking
upstream, see 'push.autoSetupRemote' in 'git help config'.

 

이는 아직 우리가 만든 브랜치가 로컬에만 존재하기 때문이다. 때문에  Git에도 내 브랜치 만들고 거기다가 커밋을 날리는 명령어가 필요한 것이다. 해당 명령어는 git push 뒤에 --set-upstream origin {새로만든 브랜치 이름} 부분을 추가 하면 된다. 그래서 이렇게 나오면 저 명령어를 복사해서 커맨드에 붙여넣기해서 실행한다. 해결 방법은 이리 간단하긴 하다.

 

하지만 언제까지 이렇게 복붙하는 과정을 할것인가? 프로출신들은 그렇게 시간이 남아나지 않는다! 그냥 git push 명령어만 써도 알아서 만들어주는 세팅이 필요하다!!! (시간 부족 아니어도 은근 귀찮다)

 

 

해결책

 $ git config --global push.autoSetupRemote true

 

이렇게 git config에 해당 세팅을 추가해주면 앞으로 저 에러는 바이바이쥐~

이젠 한방에 에러없이 git에 올려버리.

 

728x90
728x90

수강신청이 생각보다 마냥 쉽지는 않아서 신청을 못하면 어떻게 해야할까. 서울숲복합문화센터를 이용할 수 없는걸까?

 

절대 그렇지 않다. 볼링과 수영은 일일로 등록을 해서 사용할 수 있게끔 해 놓아서 해당 프로그램을 즐킬 수 있다. 

 

1. 볼링

볼링장 이용 안내

휴관일평일(화~금)토요일(30% 할증)일요일(30% 할증)

‣ 매주 월요일, 법정공휴일운영시간: 10:00 ~ 21:00운영시간: 10:00 ~ 20:00운영시간: 09:30 ~ 17:30

1게임당 이용료‣ 성 인: 3,000원
‣ 청소년,어린이: 2,100원‣ 성 인: 3,900원
‣ 청소년,어린이: 2,700원‣ 성 인: 3,900원
‣ 청소년,어린이: 2,700원

 

볼링장은 매번 일일 신청이 가능하다. 추가로 참고사항은 아래와 같다.

- 볼링화대여는 천원이다!

- 1팀 최대 3게임까지 가능하다

- 사전 예약은 불가하고 현장 선착순이다!

 

2. 자유수영

 3층 안내데스크 옆 키오스크에서 발권시간 기준 선착순 결제 및 입장 진행됩니다.

주말

 

참고로 수영은 경쟁력이 빡세다고 하니.. 미리 가서 줄서거나 하는 노오력이 조금 필요하다!! 그나마 7-8월이 제일 빡신 기간이라고 하니 9월 부터는 조금의 여유를 가질 수 있지 않을까 한다.

 

위 방법으로 한번씩 체험해보고 나서, 더욱 전의를 불태워서 수강신청을 노린다면 좋은 결과가 따라올 수 있지 않나 싶다! 무운을 빕니다 :)

728x90
728x90

 

Form 관리를 리엑트에서 관리하기는 생각보다 귀찮다.

각각의 Input에서 발생될 onChange 함수를 달아주어야 하기도 하고 제출할때 validation 체크에서 부터 에러까지, 하나씩 다 챙겨줘야 한다. 이때 보통 useState를 사용하는데 이는 input 값이 매순간 해당 범위가 리렌더링되게끔 만든다. 

// useState를 사용한 form 관리
const [value, setValue] = useState<string>();

const onClickHandler = (e) => {
	setState(e.target.value)
}
...
<input value={value} onChange={(e) => onClickHandler(e)} />
..

 

물론 해당 리렌더링이 영향에 엄청난 영향을 미치고  코드가 엄청 길어지는건 아니지만, 짧게 관리할 수 있다면 가독성도 늘어나고 효율성도 늘어나지 않을까 한다. 여기에 적합한 라이브러리 react-hook-form 이다. 이 라이브러리는 폼 관리를 단순하게 만들어줄 뿐만 아니라 퍼포먼스 부분에서도 이점을 가져다 준다. useState를 이용해서 관리시에 연결된 여러 값에서 리랜더링이 발생한다. 하지만 이걸 사용하면 현재 변동되고 있는 값에 관련된 부분만 리랜더링이 진행된다. 

 

 

설치

$ npm install react-hook-form

 

사용

useForm

useForm 훅을 선언해주면 바로 사용가능하다. 값을 업데이트 해주는 register 함수를 불러오고, defaultValues로 기본값을 선언해주면 준비 끝이다. 그 후에는 input 태그에 아래와 같이 {...register('원하는 키값')} 을 추가해주면 input 안에 값이 변경될 때마다 qwer 값을 맞춰서 수정해준다.

import { useForm, SubmitHandler } from "react-hook-form"

const { register } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
  
 <input {...register('qwer')} />

 

watch

"그럼 값이 잘 바뀌는지 어떻게 확인하죠? 보통 콘솔로 값찍는데.. 주어진 변수가 없는데..?"

우리에겐 watch라는 함수가 있다. 이 또한 useForm 훅과 함께한다. watch 함수를 선언하고 watch('지켜보고자 하는 키값')을 콘솔 등에 찍어주면 해당 값이 변경될때마다 해당 값을 체크해준다. 값을 확인해보고 싶거나 하나하나 들어오는 인풋을 체크해야 할때 유용하게 쓰일 친구다.

import { useForm, SubmitHandler } from "react-hook-form"

const { register, watch } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
 
 console.log(watch('qwer'))
  
 <input {...register('qwer')} />

 

setValue

하나의 변경으로 여러 값을 수정하고 싶을때는 어떻게 할까? 그럴땐 setValue를 사용해준다. (useState 사용할때 많이 선언했던 기억이..)

setValue('키값', 바뀌는 값)로 함수를 사용해서 여러 값을 변경시킬 수 있다.

참고로 {...register("-- ")} 를 여러개 사용하면 가장 마지막 키값에 관련된 값과만 연결되는것에 주의!! 하나씩만 사용해야한다.

import { useForm, SubmitHandler } from "react-hook-form"

const { register, watch, setValue } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
 
 console.log(watch('qwer'))
  
 <input onChange={(e) => {
   setValue('qwer', e.targe.value)}; 
   setValue('rewq', e.targe.value)};
 }
 />
 
 <input 
   {...register('qwer')}  // 기능 X
   {...register('rewq')}  // 기능 ㅇ
 />

 

handleSubmit

이제 정해진 값을 제출할 차례이다. 이는 함수 이름부터 제출인 handleSubmit을 사용한다. 해당 함수에 전체 data 값을 다 가지고 있으니  바로 아래와같이 사용해주면 된다. console.log 대신에 원하는 api 호출 등의 액션을 추가해주면 끝!

import { useForm, SubmitHandler } from "react-hook-form"

const { register, watch, setValue } = useForm({
    defaultValues: {
      qwer: '',
      rewq: '',
    },
  });
 
 const check = (data: any) => {
    console.log(data);
  };
  
 <input onChange={(e) => {
   setValue('qwer', e.targe.value)}; 
   setValue('rewq', e.targe.value)};
 }
 />
 
 <input 
   {...register('qwer')} 
 />
 
 <button onClick={handleSubmit(check)}>제출</button>

 

 

간단히 필요한 함수들을 알아보았다. 여기에 더 세부적인 기능은 아래 공홈을 한번 읽어보는걸 추천한다

https://react-hook-form.com/ 

 

React Hook Form - performant, flexible and extensible form library

Performant, flexible and extensible forms with easy-to-use validation.

react-hook-form.com

 

728x90
728x90
<span>{'안녕하세요\n나는\n개발하는 리트리버 입니다.'}</span>

 

위 코드를 돌리면 어떤 결과가 나올까? 줄바꿈 되지 않은 채로 출력된다.

"안녕하세요 나는 개발하는 리트리버 입니다."

개발중에 console 이나 alert 등에 사용하면 원하는 줄바꿈 값을 얻을 수 있지만, DOM에서 렌더링 될때는 우리가 원하는 기능을 하지 못한다. HTML이 '\n'을 줄바꿈으로 해석하지 않기 때문에 단순하게 띄어쓰기로 대체된다. (혹은 무시로..)

물론 \n을 사용하지 않고 줄바꿈을 할 수 있다면 좋다. 태그를 아예 다르게 쓰거나 해서 아예 다른 컴포넌트로 렌더링 되게끔 한다면 간단하다. 하지만 가끔 그렇게 사용하기 난감한 상황들이 좀 있다. map 함수로 배열값을 <li> 로 렌더링 되게끔 구성된 컴포넌트가 있다고 생각해보자.

const =['안녕', '이런\n저런']

LIST.map((item) => <li>{item}<.li>)

 

이렇게 되면 이런과 저런 사이에 줄바꿈을 넣기위해서 컴포넌트를 분리한다던가 하는 방법은 애매해져 버린다.

이럴때 두 가지 방법을 사용할 수 있다.

 

<pre> 사용하기

pre 태그는 preformatted text 태그로, HTML에 표시된 텍스트 그대로를 표시하고자 할때 사용되는 태그이다. 따라서 줄바꿈 뿐만 아니라 탭, 스페이스 등의 텍스트 상태를 있는 그대로 보여준다. 

LIST.map((item) => <li><pre>{item}</pre><.li>)

 

Input (pre 태그 예시)

<pre>
  This is a line of text.
    This line is indented with spaces.
  This line
  has line breaks.
</pre>

 

Output

This is a line of text.
    This line is indented with spaces.
 This line
 has line breaks.

 

white-space: pre-wrap 사용하기

css를 이용해서도 처리할 수 있다. white-space는 여백을 어떻게 처리하는가에 대해 다룬다. 여기서 pre-wrap 설정을 해주면

pre 태그와 같은 기능을 한다고 볼 수 있다. pre 라고만 설정해도 된다. pre-wrap과 pre 의 차이는, 글이 부모 사이즈를 넘어갔을때 줄을 바꿀지 (pre-wrap) 아니면 넘어갈지(pre)를 정해준다. 보통.. pre-wrap이겠죠?

 

728x90
728x90

파비콘

파비콘은 페이지를 열때 상당 탭에 표시되는 작은 아이콘을 말한다. 아이콘으로 해당 사이트를 식별할 수 있게 해주는 역할을 한다.

해당 아이콘만 보고도 직관적으로 구글페이지인지, 티스토리 페이지인지 한번에 인지할 수 있다. 오늘은 나만의 페이지에 나만의 파비콘을 추가하는 플로우에 대해서 가볍게 알아보고자 한다. 자신만의 페이지를 만들때 이런 부분도 체크해서 만들어준다면 페이지의 완성도를 더 높일 수 있지 않을까 싶다.

 

 

이미지 생성

https://www.canva.com/

나는 위의 페이지를 이용해서 나만의 이미지를 먼저 만들었다. 멋쟁이 ai가 만들어주는 이미지이다. 나는 페이지 이름다운 이미지를 만들어 달라고 부탁했다. 생각보다 웅장한 댕댕이가... 참고로 유료 버전에는 이미지를 더 야무지게 만들어줄 옵션들이 있다. 이미지의 주변을 투명하게(transparent) 로 바꿀 수도 있는데, 이는 생각보다 영향을 미치기는 한다.

 

 

 

이는 미리 보는 결과물인데, 구글이나 다른 페이지와는 다르게 원하는 이미지 주변으로 배경까지 하얀색이 들어가있는것을 확인할 수 있다. 

고퀄을 원한다면 유료버전을 사용하거나, 다른 방법을 이용해야할듯하다! (나는 일단 만드는것에 조금 집중!)

웅장력 맥스;

 

여러 포맷으로 변환

https://www.favicon-generator.org/

해당 페이지에 들어가서 원하는 이미지를 올려준다.

 

 

그리고 'Download the generated favicon' 을 클릭하면 파일을 다운로드 할 수 있다.

여러 형식으로 바꿔주는데, 우리에게 필요한건 확장자가 .ico 인 파일뿐!

 

파비콘 반영하기

React :  프로젝트를 만들면 favicon.ico 파일이 있다. 해당 파일을 교체해주면 된다

티스토리 : 관리 > 블로그 설정 에서 파비콘 파일을 넣어주면 된다! 

 

그러고 나면 원하는 결과를 확인할 수 있다!

 

728x90
728x90

또 나야~

 

1, 설치 및 사용  : https://choq.tistory.com/86

2, 큰 상태 관리 : https://choq.tistory.com/87

 

 

Zustand의 이어지는 세 번째 이야기이다.

웹 기준으로 간단하게 store를 만들고 상태를 지정해서 사용하다가, 페이지를 새로고침 하면 어떻게 될까?  zustand는 다른 상태관리 라이브러리와 마찬가지로 메모리에 저장된다. 때문에 페이지가 새로침되면 js 환경도 새로고침되면서 초기의 값으로 상태가 변경된다. 새로고침으로 상태가 초기화 된다면.. 댓츠 노우노우. 우리는 새로고침에도 굳건하게 유지되는 상태가 필요하다. 오늘은 이런 상태에서도 유지되는 zustand 세팅을 알아보려고 한다. 오늘 이용할 친구는 persist 이다.

 

Persist

이는 상태가 업데이트 될 때마다 해당 값을 지정된 스토리지에 저장하고 일반적으로는 로컬 스토리지에 해당 값을 저장한다. 

형식은  create 함수 안에 persist(()=>(  zustand action 함수  ),{   persist 옵션  }) 의 형식이다. 

import create from "zustand";
import { persist } from "zustand/middleware";

// persist 적용 store
const useStore = create(
  persist(
    (set) => ({
      count: 0,
      increase: () => set((state) => ({ count: state.count + 1 })),
      decrease: () => set((state) => ({ count: state.count - 1 })),
    }),
    {
      name: "count-storage", // 로컬 스토리지에 저장되는 키 이름
      storage: createJSONStorage(() => localStorage), // 사용할 스토리지 (기본값: localStorage)
    }
  )
);
// persist 미적용 store
const useStore = create((set) => ({
  count: 0,
  increase: () => set((state) => ({ count: state.count + 1 })),
  decrease: () => set((state) => ({ count: state.count - 1 })),
}));

 

위 처럼 persist를 사용하여 store를 작성하면 페이지를 새로고침 하더라도 상태값이 계속 유지된다!

옵션부분에는 추가적으로 아래와 같이 설정할 수 있다!

 

persist 옵션

name

필수 옵션으로 저장될 스토리지의 이름을 설정한다.

onRehydrateStorage

스토리지가 hydrated 되는과정 (시작과 끝)을 체크한다.

storage

 optional한 영역으로 해당 값이 디폴트이기 때문에 꼭 쓰지 않아도 된다. 다른 storage를 사용하게 된다면 여기에 추가!

partialize

저장할 상태를 일부만 선택할 수 있다.

 

 

 

아래 공홈에 들어가면 디테일한 코드까지 확인해볼 수 있다!

https://zustand.docs.pmnd.rs/integrations/persisting-store-data#storage

 

 

 

 

 

 

 

 

 

 

 

728x90
728x90

 

Zustand 1, 설치 및 사용해보기 : https://choq.tistory.com/86 

 

Zustand를 사용해서 객체 자체를 저장하는 과정은 매우 간단하다.

근데 객체의 깊이가 깊어진다면 코드를 귀찮게 짜야하는 상황이 생긴다. 우리가 관리하는 상태 객체가 아래와 같다고 생각해보자.

 

type State = {
  deep: {
    nested: {
      obj: { count: number }
    }
  }
}

 

일반적인 접근 방식

React에서 useState를 이용해서 객체를 관리할 때와 같이 스프레드 연산자( ... )  를 이용해서 기존의 값을 유지시켜줘야 한다.

이는 zustand에서도 똑같다.

normalInc: () =>
    set((state) => ({
      deep: {
        ...state.deep,
        nested: {
          ...state.deep.nested,
          obj: {
            ...state.deep.nested.obj,
            count: state.deep.nested.obj.count + 1
          }
        }
      }
    })),

많이들 사용하고 익숙한 방식이다. 언제나 느끼지만...너무 길다! 

 

Immer

Immer를 사용하면 이런 값을 편하게 관리할 수 있다. 물론 Immer는 Zustand에 국한되는 것은 아니고 React나 Redux에서도 위와 같은 상황에 사용할 수 있다. 사용시에는 npm install 로 따로 다운로드 해주어야 한다는것 잊지 말기!!

$ npm install immer
or
$ yarn add immer

 

immer를 이용해서 사용하는 방법은 크게 2개로 볼 수 있다. 액션에 직접 선언해주기 or 상태 전체에 선언해주기

 

액션에 직접 선언하기

store 내부 상태를 변경하는 함수에 직접 immer를 연결하는 방식이다. set함수와 함께 produce를 사용한다.

특정 액션에서만 해당 기능을 원한다면 아래와 같이 추가해주면 된다. 비교를 위해 produce를 적용한 함수와 미적용 함수를 같이 추가해두었다.

import produce from 'immer';

....
// immer 미적용 Action 함수
[Action 함수1]: () =>
    set((state) => {
      state.deep.nested.obj.count += 1;
    }),

// immer 적용 Action 함수
[Action 함수2]: () =>
    set(produce((state) => {
      state.deep.nested.obj.count += 1;
    })),

 

전체 객체에 선언하기

액션 함수 하나하나에 모두 다 추가하는게 귀찮고, 모든 액션함수가 위와같이 작동을 원하면 immer를 전체 상태에 선언해주면 된다.

const useStore = create<State>()(
  immer((set) => ({
    deep: {
      nested: {
        obj: {
          count: 0,
        },
      },
    },
    inc: () =>
      set((state) => {
        state.deep.nested.obj.count += 1;
      }),
  }))
);

위 처럼 create 하는 상단부에 immer를 선언해주면 원하는 결과를 얻을 수 있다. 

 

위 두가지 방법은 본인의 상황에 따라서 맞게 적용해주면 되겠다!

 

 


 

그럼 React에서는 Immer를 어떻게 사용할까? 상태 관리에 가장 많이 사용하는 useState를 이용한 사례는 아래와 같다.

 

// useState 사용시
const [state, setState] = useState({
  a: 1
  b: 2
});

const onClick = useCallback(() => {
  setTodo(
    produce(temp => {
      temp.a = 2;
    })
  );
}, []);

 

 

Zustand 공홈 : https://zustand.docs.pmnd.rs/guides/updating-state#deeply-nested-object

 

 

728x90

+ Recent posts