react-native-recaptcha-that-works icon indicating copy to clipboard operation
react-native-recaptcha-that-works copied to clipboard

RFC add promise based method to obtain the token

Open troZee opened this issue 3 years ago • 3 comments

Hello, Thank you for this library 🥇 I would like to propose another interesting feature to this library.

Description

Image the case, when the library is in invisible state and I would like to display the captcha, once user clicked on submit button in the form. When the submission is completed, then we can make an api call to apply the changes. So far I need to handle it using the callback, which is not the cleanest way to do it. Hence I would like to improve it using async/await handle

Posible implementation

Create a ref, which will be responsible for storing the promise

    const $promiseToken = useRef<{
      resolve: (value: GetToken | PromiseLike<GetToken>) => void;
      reject: (arg: any) => void;
    } | null>();

Move code to the separate method. It will be used in both places

    const onOpen = useCallback(() => {
      setVisible(true);
      setLoading(true);
      $isClosed.current = false;
    }, []);

Add new async method getToken, which will be responsible for opening the modal and fetching the token This promise will be stored in the ref called $promiseToken

    useImperativeHandle(
      ref,
      () => ({
        open: () => {
          onOpen();
        },
        close: handleClose,
        getToken: () => {
          onOpen();
          return new Promise<GetToken>(
            (resolve, reject: (arg: any) => void) => {
              $promiseToken.current = { resolve, reject };
            }
          );
        },
      }),
      [handleClose, onOpen]
    );

Handle this promise in handleMessage method

    const handleMessage = useCallback(
   (content) => {
     try {
       const payload = JSON.parse(content.nativeEvent.data);
       if (payload.close) {
         if (isInvisibleSize) {
           handleClose();
         }
       }
       if (payload.load) {
         handleLoad(...payload.load);
       }
       if (payload.expire) {
         //@ts-ignore
         onExpire?.(...payload.expire);
       }
       if (payload.error) {
         handleClose();
         //@ts-ignore
         onError?.(...payload.error);
         $promiseToken?.current?.reject(payload.error);
       }
       if (payload.verify) {
         handleClose();
         //@ts-ignore
         onVerify?.(...payload.verify);
         const token = payload?.verify?.[0] || '';
         $promiseToken?.current?.resolve({ token: token });
       }
     } catch (err) {
       $promiseToken?.current?.reject(err);
     }
   },
   [onVerify, onExpire, onError, handleClose, handleLoad, isInvisibleSize]
 );

After everything, we need to clean up a little bit to avoid memory leak

    React.useEffect(() => {
      return () => {
        $promiseToken.current = null;
      };
    }, []);

Before

const App = () => {
  const size = 'normal';
  const [key, setKey] = useState('<none>');

  const $recaptcha = useRef();

  const handleOpenPress = useCallback(() => {
    $recaptcha.current.open();
  }, []);

  const handleClosePress = useCallback(() => {
    $recaptcha.current.close();
  }, []);


  useState(() => {
   // handle submit here (api call), which is not the best solution. I would prefer to have it all at one place
  },[key])

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.safeArea}>
        <View
          contentInsetAdjustmentBehavior="automatic"
          style={styles.container}>
          <Button onPress={handleOpenPress} title="Open" />
          <Text>Token: {key}</Text>
          <Text>Size: {size}</Text>
        </View>

        <Recaptcha
          ref={$recaptcha}
          lang="en"
          headerComponent={<Button title="Close" onPress={handleClosePress} />}
          siteKey="6LejsqwZAAAAAGsmSDWH5g09dOyNoGMcanBllKPF"
          baseUrl="http://127.0.0.1"
          size={size}
          theme="light"
          onLoad={() => alert('onLoad event')}
          onClose={() => alert('onClose event')}
          onError={(err) => {
            alert('onError event');
            console.warn(err);
          }}
          onExpire={() => alert('onExpire event')}
          onVerify={(token) => {
            alert('onVerify event');
            setKey(token);
          }}
        />
      </SafeAreaView>
    </>
  );
};

After

const App = () => {
  const size = 'normal';
  const [key, setKey] = useState('<none>');

  const $recaptcha = useRef();

  const handleOpenPress = useCallback(() => {
    const response = await $recaptcha?.current?.getToken();
    if (response?.token) {
    // do some api calls etc 
    } else {
    // do some api calls etc 
      Alert.alert('Title', 'Something went wrong');
    }  }, []);

  const handleClosePress = useCallback(() => {
    $recaptcha.current.close();
  }, []);

  return (
    <>
      <StatusBar barStyle="dark-content" />
      <SafeAreaView style={styles.safeArea}>
        <View
          contentInsetAdjustmentBehavior="automatic"
          style={styles.container}>
          <Button onPress={handleOpenPress} title="Open" />
          <Text>Token: {key}</Text>
          <Text>Size: {size}</Text>
        </View>

        <Recaptcha
          ref={$recaptcha}
          lang="en"
          headerComponent={<Button title="Close" onPress={handleClosePress} />}
          siteKey="6LejsqwZAAAAAGsmSDWH5g09dOyNoGMcanBllKPF"
          baseUrl="http://127.0.0.1"
          size={size}
          theme="light"
          onLoad={() => alert('onLoad event')}
          onClose={() => alert('onClose event')}
          onError={(err) => {
            alert('onError event');
            console.warn(err);
          }}
          onExpire={() => alert('onExpire event')}
          onVerify={(token) => {
            alert('onVerify event');
            setKey(token);
          }}
        />
      </SafeAreaView>
    </>
  );
};

WDYT about above approach ? Feel free to comment.

troZee avatar Jul 08 '21 09:07 troZee

~~I don't know why, github didn't notify me of your message.~~

Hi @troZee, thanks for the suggestions.

I already had this change in mind, but at the moment I don't have time to put it into practice.

If you are interested in creating a pull request, I will be happy to review it.

I would just add error handling with promise.reject to your suggestion.

douglasjunior avatar Jul 29 '21 11:07 douglasjunior

~~I don't know why, github didn't notify me of your message.~~

Hi @troZee, thanks for the suggestions.

I already had this change in mind, but at the moment I don't have time to put it into practice.

If you are interested in creating a pull request, I will be happy to rate it.

I would just add error handling with promise.reject to your suggestion.

Sure, I will prepare a PR for it, bc i added this additional code as a pack package. In prepared PR, we will discuss about it

troZee avatar Jul 29 '21 11:07 troZee

@troZee Whatever happened to the PR?

alexrabin avatar Sep 01 '23 20:09 alexrabin