Tìm kiếm trang web

javascript - Hàm được truyền làm đối số cho Hook React tùy chỉnh đang gây ra sự cố


Nhu cầu:

Tôi đang sử dụng RedwoodJS để tạo một ứng dụng Fullstack. Nó cung cấp cho chúng tôi hook useAuth() cho biết trạng thái isAuthenticated để biết người dùng đã đăng nhập hay chưa.

Tôi muốn thực hiện một số truy vấn trên phía web mỗi khi người dùng đăng nhập. Không phải mỗi khi isAuthenticated từ useAuth() thay đổi. (ví dụ: khi tải trang, isAuthenticated được đặt thành false từ undefined.. nhưng điều này không có nghĩa là người dùng đã đăng xuất. Nếu tôi chỉ muốn chạy một số hàm nhất định khi đăng xuất thì sao?)

Giải pháp đã thử:

Tôi đã viết hook tùy chỉnh này:

export type CurrentUser = ReturnType<typeof useAuth>['currentUser']

interface HookProps {
  // similarly for onLogout
  onLogin?: (currentUser: CurrentUser) => void
}

export function useAuthenti(props: HookProps): CurrentUser | false {
  const { onLogin } = props
  const { isAuthenticated, currentUser } = useAuth()
  const wasAuthenticated = usePrevious(isAuthenticated);

  const [currentUserOrFalse, setCurrentUserOrFalse] = useState<CurrentUser | false>(false)

  useEffect(() => {

    console.log(`isAuthenticated CHANGED: ${wasAuthenticated} => ${isAuthenticated}`)

    if (isAuthenticated) {
      setCurrentUserOrFalse(currentUser)
      if (wasAuthenticated === undefined) {
        console.log(`false login 1`)
      } else if (wasAuthenticated === false) {
        console.log(`real login [MAKE API CALLS]`)
        if (onLogin) {
          console.log(`1. [inside] calling onlogin`)
          onLogin?.(currentUser)
          console.log(`4. [inside] called onLogin`)
        }
      } else if (wasAuthenticated === true) {
        console.log(`false login 2`)
      } else {
        console.log(`false login 3`)
      }
    } else {
      setCurrentUserOrFalse(false)
      if (wasAuthenticated === undefined) {
        console.log(`false logout 1`)
      } else if (wasAuthenticated === false) {
        console.log(`false logout 2`)
      } else if (wasAuthenticated === true) {
        console.log(`real logout [MAKE API CALLS]`)
      } else {
        console.log(`false logout 3`)
      }
    }

  }, [isAuthenticated])

  return currentUserOrFalse
}

và tôi đang sử dụng hook này như sau:

export function Initialize({ children }: ComponentProps) {

  const [getMyData, { loading: loading_MyData, data: data_MyData }] = useLazyQuery(MY_DATA_QUERY)
  const [getAllPosts, { loading: loading_AllPosts, data: data_AllPosts }] = useLazyQuery(ALL_POSTS_QUERY)

  useAuthenti({
    onLogin: (currentUser: CurrentUser) => {
      console.log(`2. [outside] onLogin start`)
      getMyData()
      getAllPosts()
      console.log(`3. [outside] onLogin done`)
    },
  })

  useEffect(() => {
    if (data_MyData && data_AllPosts) {
      console.log(data_MyData)
      console.log(data_AllPosts)
    }
  }, [data_MyData, data_AllPosts])

  return (
    <>
      {children}
    </>
  )
}

Vấn đề:

Trong cách sử dụng ở trên, như bạn có thể thấy tôi đang cung cấp thuộc tính hàm onLogin cho hook tùy chỉnh useAuthenti. Bởi vì tôi muốn hàm đó chạy KHI ĐĂNG NHẬP và thực hiện các cuộc gọi API trong đó.

Tuy nhiên, mã không hoạt động như mong đợi mỗi lần. Đôi khi nó hoạt động, những lúc khác thì không. (cũng hơi lạ.. có vẻ như là một điều kiện tranh chấp?)

Khi nó không hoạt động, tôi không thấy bất kỳ nhật ký console nào (gợi ý rằng onLogin chưa bao giờ được gọi), NHƯNG, trong tab mạng tôi CÓ thấy các cuộc gọi được thực hiện bởi createHttpLink.js:100. Nó cũng bị lỗi. Trong trường hợp này (khi nó không hoạt động như mong đợi), tôi thấy cuộc gọi đăng nhập người dùng thành công NHƯNG cuộc gọi getCurrentUser của redwood js không được thực hiện. (bạn có thể bỏ qua phần redwoodjs nếu bạn không quen với redwood js, chỉ tập trung vào phần hook) Làm cách nào các API bên trong onLogin chạy mà không có bất kỳ nhật ký console nào xung quanh nó?

Gỡ lỗi tab Mạng

Hộp đỏ hiển thị lần thử đăng nhập thứ nhất (không thành công) Hộp xanh hiển thị lần thử đăng nhập thứ hai (thành công)

Ghi chú bổ sung:

Rõ ràng, chỉ việc truyền các hàm dưới dạng đối số đang khiến chúng chạy NGAY CẢ khi tôi không chạy chúng bên trong hook. Tại sao các hàm được truyền làm đối số cho hook lại tự chạy?

Cách tôi đang tạo một React Hook tùy chỉnh có sai không? Tôi đang làm sai điều gì ở đây nếu ai đó có thể cho tôi biết.

Có phải có điều kiện tranh chấp vì đôi khi nó hoạt động và đôi khi thì không?

Tôi tiến hành như thế nào?

Câu trả lời #1

Tôi không hiểu tại sao, nhưng việc viết một hook tùy chỉnh để thay thế useLazyQuery mà trả về một Promise đã sửa tất cả các vấn đề.

Chuỗi thảo luận Github này đã giúp: https://github.com/apollographql/react-apollo/issues/3499

Đặc biệt là câu trả lời này: https://github.com/apollographql/react-apollo/issues/3499#issuecomment-539346982

Mà tôi đã mở rộng để hỗ trợ Typescript và các trạng thái như loading error và data. (phiên bản của anh ấy là useLazyQuery, hook mở rộng là useLazyQueryModded)

useLazyQueryModded.tsx

import { useState } from 'react';
import { DocumentNode, ApolloError, useApolloClient, OperationVariables } from '@apollo/client';

type QueryVariables = Record<string, any>;

type UseLazyQueryResult<TData, TVariables extends QueryVariables> = [
  (variables?: TVariables) => void,
  { data: TData | undefined, error: ApolloError | undefined, loading: boolean }
];

function useLazyQuery<TData = any, TVariables = OperationVariables>(query: DocumentNode) {
  const client = useApolloClient()
  return React.useCallback(
    (variables: TVariables) =>
      client.query<TData, TVariables>({
        query: query,
        variables: variables,
        fetchPolicy: 'cache-first'
      }),
    [client]
  )
}

function useLazyQueryModded<TData = any, TVariables extends QueryVariables = QueryVariables>(
  query: DocumentNode
): UseLazyQueryResult<TData, TVariables> {
  const [loading, setLoading] = useState(false);
  const [data, setData] = useState<TData | undefined>(undefined);
  const [error, setError] = useState<ApolloError | undefined>(undefined);

  const runQuery = useLazyQuery<TData, TVariables>(query);

  const executeQuery = async (variables?: TVariables) => {
    setLoading(true);
    setData(undefined);
    setError(undefined);

    try {
      const result = await runQuery(variables);
      setData(result.data);
    } catch (error) {
      setError(error);
    } finally {
      setLoading(false);
    }
  };

  const result = { data, error, loading };

  return [executeQuery, result];
}


export { useLazyQueryModded };

Nếu ai đó có thể giải thích tại sao useLazyQuery không trả về một Promise hoặc tại sao ngay cả khi nó không trả về thì mã vẫn đôi khi hoạt động, tôi sẽ bớt cảm giác khó chịu này.