# React Query basic

reference: <https://jforj.tistory.com/search/React%20Query>

***

#### useQuery

* useQuery는 **React Query를 이용해 서버로부터 데이터를 조회해올 때 사용**합니다.

  ※ 데이터 조회가 아닌 데이터 변경 작업을 할 때는 \*\*[useMutation](https://jforj.tistory.com/244)\*\*을 사용합니다. 데이터베이스로 비유하자면 select를 할 때 사용

• queryKey: queryKey의 역할은 **React-Query가 query 캐싱을 관리할 수 있도록 도와줌**

• queryFn

```tsx
// 1
const res = useQuery(queryKey, queryFn);

// 2
const res = useQuery({
    queryKey: queryKey,
    queryFn: queryFn
});
```

**option값**

```tsx
// 1
const res = useQuery(['persons'], () => axios.get('<http://localhost:8080/persons>'), {
    staleTime: 5000, // 5초
    cacheTime: Infinity, // 제한 없음
});

// 2
const res = useQuery({
    queryKey: ['persons'],
    queryFn: () => axios.get('<http://localhost:8080/persons>'),
    staleTime: 5000, // 5초
    cacheTime: Infinity // 제한 없음
});
```

**refetchOnWindowFocus 전역설정**

* 방법 1

```tsx
import * as React from 'react';
import ReactDom from 'react-dom';
import { QueryClient, QueryClientProvider } from 'react-query';
import App from './App';

const queryClient = new QueryClient(
    {
        defaultOptions: {
            queries: {
                refetchOnWindowFocus: false, // window focus 설정
            }
        }
    }
); // queryClient 생성

ReactDom.render(
    // App에 QueryClient 제공
    <QueryClientProvider client={queryClient}>
        <App />
    </QueryClientProvider>, 
    document.querySelector('#root')
);
```

* 방법 2

```tsx
// 1
const res = useQuery(['persons'], () => axios.get('<http://localhost:8080/persons>'), {
    refetchOnWindowFocus: false // window focus 설정
});

// 2
const res = useQuery({
    queryKey: ['persons'],
    queryFn: () => axios.get('<http://localhost:8080/persons>'),
    refetchOnWindowFocus: false // window focus 설정
});
```

#### query 자동실행 설정

ex)

```tsx
if(id) {
    const res = axios.get('<http://localhost:8080/person>', {
        params: {
            id: id,
        }
    })
}
```

useQuery에서는 if문을 사용하지 않고 useQuery에서 제공해주는 query 자동 실행 설정을 통해 동일한 결과를 만들어 줄 수 있다.

```tsx
// 1
const res = useQuery(['person', id], () => axios.get('<http://localhost:8080/person>', {
    params: {
        id: id,
    }
}), {
    enabled: !!id // 코드 자동 실행 설정
});

// 2
const res1 = useQuery({
    queryKey: ['person', id],
    queryFn: () => axios.get('<http://localhost:8080/person>', {
        params: {
            id: id,
        }
    }),
    enabled: !!id // 코드 자동 실행 설정
});
```

***

***

#### useMutation

useMutation은 **React Query를 이용해 서버에 데이터 변경 작업을 요청할 때 사용**합니다.※ 데이터 조회를 할 때는 useQuery를 사용데이터베이스로 비유하자면 insert, update, delete가 모두 포함

사용형태

```tsx
// 1
const savePerson = useMutation(mutationFn);

// 2
const savePerson = useMutation({
    mutationFn: mutationFn
})
```

**mutationFn**은 `mutation` Function으로 **`promise` 처리가 이루어지는 함수**입니다.

다른 말로는 `axios`를 이용해 서버에 API를 요청하는 부분

```tsx
// 1
const savePerson = useMutation((person: Iperson) => axios.post('/savePerson', person));

// 2
const savePerson = useMutation({
    mutationFn: (person: Iperson) => axios.post('/savePerson', person)
})
```

#### mutate

mutate는 **`useMutation`을 이용해 작성한 내용들이 실행될 수 있도록 도와주는 trigger 역할**

즉, `useMutation`을 정의 해둔 뒤 이벤트가 발생되었을 때 mutate를 사용

```tsx
const savePerson = useMutation((person: Iperson) => axios.post('<http://localhost:8080/savePerson>', person)); // useMutate 정의

    const onSavePerson = () => {
        savePerson.mutate(person); // 데이터 저장
    }

<Person.SaveButton onClick={onSavePerson}>저장</Person.SaveButton>
```

**onSuccess, onError, onSettled**

일반적으로 서버에 데이터 변경 요청을 하게 되면 변경 요청이 성공할 경우에 추가적인 액션을 할 수 있도록 코드를 작성.

이런 상황은 useMutation을 사용할 때도 동일하게 적용.

`async/await`을 사용할 때는 보통 다음과 같이 결괏값이 있는지를 확인한 뒤 추가 작업을 수행할 수 있는 코드를 작성

```tsx
try {
    const res = await axios.post('<http://localhost:8080/savePerson>', person);

    if(res) {
        console.log('success');
    }
} catch(error) {
    console.log('error');
} finally {
    console.log('finally');
}
```

`useMutation` 을 사용하면 다음과 같이 사용

```tsx
// 1
const savePerson = useMutation((person: Iperson) => axios.post('<http://localhost:8080/savePerson>', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
});

// 2
const savePerson = useMutation({
    mutationFn: (person: Iperson) => axios.post('/savePerson', person),
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
})
```

`onSettled`는 `finally` 구문처럼 요청이 성공하든 에러가 발생되든 상관없이 마지막에 실행되는 구간

`onSuccess, onError, onSettled`는 `useMutation`을 정의할 때만 사용할 수 있는 것이 아니라 `mutate`에서도 사용이 가능합니다.

```tsx
const onSavePerson = () => {
    savePerson.mutate(person, {
        onSuccess: () => { // 요청이 성공한 경우
            console.log('onSuccess');
        },
        onError: (error) => { // 요청에 에러가 발생된 경우
            console.log('onError');
        },
        onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
            console.log('onSettled');
        }
    }); // 데이터 저장
}
```

#### invalidateQueries

* `useQuery`에서 사용되는 `queryKey`의 유효성을 제거해주는 목적으로 사용
* `queryKey`의 유효성을 제거해주는 이유는 **서버로부터 다시 데이터를 조회해오기 위함**
* 정해진 시간이 도달하지 않으면 새로운 데이터가 적재되었더라도 `useQuery`는 변동 없이 동일한 데이터를 화면에 보여줄 것입니다.
* 결국 사용자 입장에서는 데이터 생성이 제대로 되었는지에 대한 파악이 힘들기 때문에 혼란을 겪을 수 있게 됩니다.
* 해당 상황을 해결해줄 수 있는 것이 바로 `invalidateQueries`입니다.
* 데이터를 저장할 때 `invalidateQueries`를 이용해 `useQuery`가 가지고 있던 `queryKey`의 유효성을 제거해주면 캐싱되어있는 데이터를 화면에 보여주지 않고 서버에 새롭게 데이터를 요청하게 됩니다.
* 결국 데이터가 새롭게 추가되었을 때 다시 서버에서 데이터를 가져오게 되면서 추가한 데이터까지 화면에서 확인할 수 있게 됩니다.

```tsx
const queryClient = useQueryClient();  // 등록된 quieryClient 가져오기

const savePerson = useMutation((person: Iperson) => axios.post('<http://localhost:8080/savePerson>', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
        queryClient.invalidateQueries('persons'); // queryKey 유효성 제거
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
}); // useMutate 정의
```

***

#### setQueryData

* `invalidateQueries`를 사용하지 않고도 데이터를 업데이트해줄 수 있는 방법은 있습니다.

  `setQueryData`를 활용하면 되는데 `setQueryData`는 **기존에 `queryKey`에 매핑되어 있는 데이터를 새롭게 정의**해줍니다.

```tsx
const queryClient = useQueryClient();  // 등록된 quieryClient 가져오기

const savePerson = useMutation((person: Iperson) => axios.post('<http://localhost:8080/savePerson>', person), {
    onSuccess: () => { // 요청이 성공한 경우
        console.log('onSuccess');
        queryClient.setQueryData('persons', (data) => {
            const curPersons = data as AxiosResponse<any, any>; // persons의 현재 데이터 확인
            curPersons.data.push(person); // 데이터 push

            return curPersons; // 변경된 데이터로 set
        })
    },
    onError: (error) => { // 요청에 에러가 발생된 경우
        console.log('onError');
    },
    onSettled: () => { // 요청이 성공하든, 에러가 발생되든 실행하고 싶은 경우
        console.log('onSettled');
    }
}); // useMutate 정의
```

***

#### useQueries

`useQueries`는 **React Query에서 `useQuery`의 동적 병렬 쿼리 작업을 위해 사용**

`useQueries`의 사용 방법은 단순하게 `useQuery`를 배열로 넣어준다

```tsx
const ress = useQueries([
    useQuery1,
    useQuery2,
    useQuery3,
    ...
]);
```

```tsx
const res1 = useQuery(['persons'], () => axios.get('<http://localhost:8080/persons>'), {
});
const res2 = useQuery(['person'], () => axios.get('<http://localhost:8080/person>', {
    params: {
        id: 1
    }
}));
```

```tsx
const res = useQueries([
    {
        queryKey: ['persons'],
        queryFn: () => axios.get('<http://localhost:8080/persons>'),
    },
    {
        queryKey: ['person'],
        queryFn: () => axios.get('<http://localhost:8080/person>', {
            params: {
                id: 1
            }
        }),
    }
]);
```

**`useQuery`보다 `useQueries`를 사용해야 하는 상황은 동적으로 변화하는 상황**

```tsx
const getPersons = (persons: Iperson[]) => {
  const res = useQueries(
    persons.map((person) => {
      return {
        queryKey: ['person', person.id],
        queryFn: () =>
          axios.get('<http://localhost:8080/person>', {
            params: {
              id: person.id,
            },
          }),
      };
    })
  );

  return (
    <Person.Container>
      {ress &&
        ress.map((res) => {
          const person: Iperson = res.data && res.data.data;
          return (
            person && (
              <Person.Box key={person.id}>
                <Person.Title>{person.id}.</Person.Title>
                <Person.Text>{person.name}</Person.Text>
                <Person.Text>({person.age})</Person.Text>
              </Person.Box>
            )
          );
        })}
    </Person.Container>
  );
};
```

**Suspense**

React Query의 `suspense` 모드를 설정하게 되면 `useQuery`의 status, error 등을 `React.Suspense`로 대체

```tsx
// App.tsx

import * as React from 'react';
import Queries from './pages/queries';

const App = (): JSX.Element => {
  return <Queries />;
};

export default App;
```

```tsx
// queries.tsx

import axios from 'axios';
import * as React from 'react';
import { useQuery } from 'react-query';
import styled from 'styled-components';

interface Iperson {
  id: number;
  name: string;
  phone: string;
  age: number;
}

const Queries = (): JSX.Element => {
  const getPersons = () => {
    const res = useQuery(['persons'], () =>
      axios.get('<http://localhost:8080/persons>')
    );

    // 로딩 중일 경우
    if (res.isLoading) {
      return <LoadingText>Queries Loading...</LoadingText>;
    }

    // 결과값이 전달되었을 경우
    const persons: Iperson[] = res.data && res.data.data;

    return (
      <Person.Container>
        {persons &&
          persons.map((person) => {
            return (
              <Person.Box key={person.id}>
                <Person.Title>{person.id}.</Person.Title>
                <Person.Text>{person.name}</Person.Text>
                <Person.Text>({person.age})</Person.Text>
              </Person.Box>
            );
          })}
      </Person.Container>
    );
  };

  return <Wrapper>{getPersons()}</Wrapper>;
};

export default Queries;

// styled
const Wrapper = styled.div`
  max-width: 728px;

  margin: 0 auto;
`;

const LoadingText = styled.h3`
  text-align: center;
`;

const Person = {
  Container: styled.div`
    padding: 8px;
  `,

  Box: styled.div`
    border-bottom: 2px solid olive;
  `,

  Title: styled.h2`
    display: inline-block;

    margin: 0 12px;

    line-height: 48px;
  `,

  Text: styled.span`
    margin: 0 6px;
  `,
};
```

`React.Suspense`를 이용해 다음과 같이 변경

첫 번째는 App.tsx의 Queries Component가 React.Suspense로 덮여있다는 것

두 번째는 queries.tsx의 useQuery에 suspense 모드 설정이 추가

세 번째는 queries.tsx에서 로딩 중일 경우에 대한 코드가 삭제

```tsx
// App.tsx

import * as React from 'react';
import Queries from './pages/queries';

const App = (): JSX.Element => {
  return (
    <React.Suspense fallback={<div>App Loading...</div>}>
      <Queries />
    </React.Suspense>
  );
};

export default App;
```

```tsx
// queries.tsx

import axios from 'axios';
import * as React from 'react';
import { useQuery } from 'react-query';
import styled from 'styled-components';

interface Iperson {
  id: number;
  name: string;
  phone: string;
  age: number;
}

const Queries = (): JSX.Element => {
  const getPersons = () => {
    const res = useQuery(
      ['persons'],
      () => axios.get('<http://localhost:8080/persons>'),
      {
        suspense: true, // suspense 모드 설정
      }
    );

    // 결과값이 전달되었을 경우
    const persons: Iperson[] = res.data && res.data.data;

    return (
      <Person.Container>
        {persons &&
          persons.map((person) => {
            return (
              <Person.Box key={person.id}>
                <Person.Title>{person.id}.</Person.Title>
                <Person.Text>{person.name}</Person.Text>
                <Person.Text>({person.age})</Person.Text>
              </Person.Box>
            );
          })}
      </Person.Container>
    );
  };

  return <Wrapper>{getPersons()}</Wrapper>;
};

export default Queries;

const Wrapper = styled.div`
  max-width: 728px;

  margin: 0 auto;
`;

const Person = {
  Container: styled.div`
    padding: 8px;
  `,

  Box: styled.div`
    border-bottom: 2px solid olive;
  `,

  Title: styled.h2`
    display: inline-block;

    margin: 0 12px;

    line-height: 48px;
  `,

  Text: styled.span`
    margin: 0 6px;
  `,
};
```

**`suspense` 모드에서 `useQueries`를 사용**

`suspense` 모드에서 useQueries를 사용해야 하는 이유는 useQuery를 병렬로 처리하여 사용하고 있을 때 만약 **하나의 useQuery가 정상적으로 동작되지 않을 경우 그 이후에 실행될 useQuery에 영향** 을 미치며 결과적으로 올바른 화면이 보이지 않기 때문

첫 번째 요소는 걸러지고 두 번째 요소에 해당하는 결과만 화면에 노출

```tsx
import axios from 'axios';
import * as React from 'react';
import { useQueries } from 'react-query';
import styled from 'styled-components';

interface Iperson {
  id: number;
  name: string;
  phone: string;
  age: number;
}

const Queries = (): JSX.Element => {
  const getPersons = () => {
    const res = useQueries([
      {
        queryKey: ['persons'],
        queryFn: () => axios.get('<http://localhost:8080/personssss>'), // 오류
        suspense: true,
      },
      {
        queryKey: ['person'],
        queryFn: () =>
          axios.get('<http://localhost:8080/person>', {
            params: {
              id: 1,
            },
          }),
        suspense: true,
      },
    ]);

    if (res) {
      const persons: Iperson[] = res[0].data && res[0].data.data;
      const person: Iperson = res[1].data && res[1].data.data;

      return (
        <Person.Container>
                  {persons && persons.map((person) => {
              return (
                <Person.Box key={person.id}>
                  <Person.Title>{person.id}.</Person.Title>
                  <Person.Text>{person.name}</Person.Text>
                  <Person.Text>({person.age})</Person.Text>
                </Person.Box>
              );
            })}

                  {person && (
                    <Person.Box>
                        <Person.Title>{person.id}.</Person.Title>
                        <Person.Text>{person.name}</Person.Text>
                        <Person.Text>({person.age})</Person.Text>
                    </Person.Box>
                  )}
          )}
        </Person.Container>
      );
    }
  };

  return <Wrapper>{getPersons()}</Wrapper>;
};

export default Queries;

const Wrapper = styled.div`
  max-width: 728px;

  margin: 0 auto;
`;

const Person = {
  Container: styled.div`
    padding: 8px;
  `,

  Box: styled.div`
    border-bottom: 2px solid olive;
  `,

  Title: styled.h2`
    display: inline-block;

    margin: 0 12px;

    line-height: 48px;
  `,

  Text: styled.span`
    margin: 0 6px;
  `,
};
```

***

#### useInfiniteQuery

`useInfiniteQuery`는 **파라미터 값만 변경하여 동일한 `useQuery`를 무한정 호출할 때 사용**

`const res = useInfiniteQuery(queryKey, queryFn);`

* **pageParam**

`pageParam`은 **`useInfiniteQuery`가 현재 어떤 페이지에 있는지를 확인할 수 있는 파라미터 값**

```tsx
const res = useInfiniteQuery(['infinitePerson'], ({ pageParam = 5 }) =>
  axios.get('<http://localhost:8080/person>', {
    params: {
      id: pageParam,
    },
  })
);
```

기본 값은 `undefined`이기 때문에 값이 없을 경우 초기값으로 5 설정

데이터를 조회해올 때 `pageParam`값을 api 요청할 때 파라미터 값으로 넣어 사용

* **getNextPageParam과 fetchNextPage**

`getNextPageParam`과 `fetchNextPage`은 공통적으로 **다음 페이지에 있는 데이터를 조회해올 때 사용**

* getNextPageParam은 **다음 api를 요청할 때 사용될 pageParam값을 정할 수 있다**

```tsx
const res = useInfiniteQuery(
  ['infinitePerson'],
  ({ pageParam = 5 }) =>
    axios.get('<http://localhost:8080/person>', {
      params: {
        id: pageParam,
      },
    }),
  {
    getNextPageParam: (lastPage, allPages) => {
      return lastPage.data.id + 1; // 다음 페이지를 호출할 때 사용 될 pageParam
    },
  }
);
```

`lastPage`는 `useInfiniteQuery`를 이용해 호출된 가장 마지막에 있는 페이지 데이터를 의미합니다.

`allPages`는 `useInfiniteQuery`를 이용해 호출된 모든 페이지 데이터를 의미

**`return` 되는 값이 다음 페이지가 호출될 때 `pageParam` 값**

* fetchNextPage는 **다음 페이지의 데이터를 호출할 때 사용**

useInfiniteQuery의 return 값에 포함되며 다음과 같이 버튼을 클릭할 때 실행될 이벤트로 등록해줄 수 있다

```tsx
const getPersons = () => {
  const res = useInfiniteQuery(
    ['infinitePerson'],
    ({ pageParam = 5 }) =>
      axios.get('<http://localhost:8080/person>', {
        params: {
          id: pageParam,
        },
      }),
    {
      getNextPageParam: (lastPage, allPages) => {
        return lastPage.data.id + 1; // 다음 페이지를 호출할 때 사용 될 pageParam
      },
    }
  );

  // 로딩 중일 경우
  if (res.isLoading) {
    return <LoadingText>Loading...</LoadingText>;
  }

  // 결과값이 전달되었을 경우
  if (res.data) {
    return (
      <Person.Container>
        {res.data.pages.map((page) => {
          const person: Iperson = page.data;

          return (
            <Person.Box key={person.id}>
              <Person.Title>{person.id}.</Person.Title>
              <Person.Text>{person.name}</Person.Text>
              <Person.Text>({person.age})</Person.Text>
            </Person.Box>
          );
        })}
        <Person.NextButton onClick={() => res.fetchNextPage()}>
          Next
        </Person.NextButton>{' '}
        {/* 클릭 시 다음 페이지 호출 */}
      </Person.Container>
    );
  }
};
```

#### Infinite Scroll

```tsx
import axios from 'axios';
import * as React from 'react';
import { useEffect, useRef, useState } from 'react';

// info interface
interface Info {
  name: string;
  phone: string;
  age: number;
}

const App = (): JSX.Element => {
  // state
  const [infoArray, setInfoArray] = useState<Info[]>([]);

  // ref
  const observerRef = useRef<IntersectionObserver>();
  const boxRef = useRef<HTMLDivElement>(null);

  // useEffect
  useEffect(() => {
    getInfo();
  }, []);

  useEffect(() => {
    observerRef.current = new IntersectionObserver(intersectionObserver); // IntersectionObserver
    boxRef.current && observerRef.current.observe(boxRef.current);
  }, [infoArray]);

  // function
  const getInfo = async () => {
    const res = await axios.get('<http://localhost:8080/rest/getInfo>'); // 서버에서 데이터 가져오기
    setInfoArray((curInfoArray) => [...curInfoArray, ...res.data]); // state에 추가

    // console.log('info data add...');
  };

  // IntersectionObserver 설정
  const intersectionObserver = (
    entries: IntersectionObserverEntry[],
    io: IntersectionObserver
  ) => {
    entries.forEach((entry) => {
      if (entry.isIntersecting) {
        // 관찰하고 있는 entry가 화면에 보여지는 경우
        io.unobserve(entry.target); // entry 관찰 해제
        getInfo(); // 데이터 가져오기
      }
    });
  };

  // style
  const Wrapper = {
    width: '800px',
    margin: '0 auto',
  };

  const Box = {
    border: '1px solid olive',
    borderRadius: '8px',
    boxShadow: '1px 1px 2px olive',
    margin: '18px 0',
  };

  const BoxTable = {
    borderSpacing: '15px',
  };

  const Title = {
    fontWeight: 700,
  };

  return (
    <div style={Wrapper}>
      {infoArray.map((info, index) => {
        if (infoArray.length - 5 === index) {
          // 관찰되는 요소가 있는 html, 아래에서 5번째에 해당하는 박스를 관찰
          return (
            <div style={Box} ref={boxRef} key={index}>
              <table style={BoxTable}>
                <tbody>
                  <tr>
                    <td style={Title}>이름</td>
                    <td>{info.name}</td>
                  </tr>

                  <tr>
                    <td style={Title}>전화번호</td>
                    <td>{info.phone}</td>
                  </tr>

                  <tr>
                    <td style={Title}>나이</td>
                    <td>{info.age}</td>
                  </tr>
                </tbody>
              </table>
            </div>
          );
        }
        // 관찰되는 요소가 없는 html
        return (
          <div style={Box} key={index}>
            <table style={BoxTable} key={index}>
              <tbody>
                <tr>
                  <td style={Title}>이름</td>
                  <td>{info.name}</td>
                </tr>

                <tr>
                  <td style={Title}>전화번호</td>
                  <td>{info.phone}</td>
                </tr>

                <tr>
                  <td style={Title}>나이</td>
                  <td>{info.age}</td>
                </tr>
              </tbody>
            </table>
          </div>
        );
      })}
    </div>
  );
};

export default App;
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://adam-37.gitbook.io/joomadeung/react-query/react-query-v4/react-query-basic.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
