typesense / typesense-js

JavaScript / TypeScript client for Typesense
https://typesense.org/docs/api
Apache License 2.0
393 stars 74 forks source link

Request to Node 0 failed due to "undefined Network Error" | Error: Exception in HostFunction: Malformed calls from JS: field sizes are different. #189

Open walter-ayala opened 8 months ago

walter-ayala commented 8 months ago

Description

I'm using TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter' and InstantSearch, useInfiniteHits, useSearchBox from 'react-instantsearch-core' in my react native app, when i run the app for ios that part works, but in android don't working

Information about it

image image

That is my dependencies in my package.json

"dependencies": {
    "@apollo/client": "^3.8.4",
    "@react-native-async-storage/async-storage": "^1.19.3",
    "@react-native-firebase/app": "^18.5.0",
    "@react-native-firebase/auth": "^18.5.0",
    "@react-native-firebase/messaging": "^18.5.0",
    "@react-native-masked-view/masked-view": "^0.2.9",
    "@react-navigation/bottom-tabs": "^6.2.0",
    "@react-navigation/native": "^6.1.8",
    "@react-navigation/native-stack": "^6.9.14",
    "@reduxjs/toolkit": "^1.9.6",
    "@sentry/cli": "^2.22.3",
    "@sentry/react-native": "^5.14.1",
    "i18next": "^23.5.1",
    "mixpanel-react-native": "^2.2.5",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-fast-compare": "^3.2.2",
    "react-i18next": "^13.2.2",
    "react-instantsearch-core": "^7.3.0",
    "react-instantsearch-hooks": "^6.47.3",
    "react-native": "^0.72.7",
    "react-native-autocomplete-dropdown": "^3.1.0",
    "react-native-bootsplash": "^5.1.3",
    "react-native-bouncy-checkbox": "^3.0.7",
    "react-native-config": "^1.5.1",
    "react-native-countdown-circle-timer": "^3.2.1",
    "react-native-date-picker": "^4.2.6",
    "react-native-device-info": "^10.12.0",
    "react-native-geolocation-service": "^5.3.0-beta.4",
    "react-native-gesture-handler": "^2.13.1",
    "react-native-image-picker": "^5.6.1",
    "react-native-image-slider-box": "megamaxs1234/react-native-image-slider-box",
    "react-native-indicators": "^0.17.0",
    "react-native-keyboard-aware-scroll-view": "^0.9.5",
    "react-native-linear-gradient": "^2.8.3",
    "react-native-maps": "^1.7.1",
    "react-native-mask-input": "^1.2.3",
    "react-native-modalize": "^2.0.13",
    "react-native-pdf": "^6.7.1",
    "react-native-portalize": "^1.0.7",
    "react-native-progress": "^5.0.0",
    "react-native-ratings": "^8.1.0",
    "react-native-reanimated": "^3.6.1",
    "react-native-reanimated-carousel": "^3.1.0",
    "react-native-safe-area-context": "^4.7.2",
    "react-native-screens": "^3.25.0",
    "react-native-share": "^9.4.1",
    "react-native-skeleton-placeholder": "^5.0.0",
    "react-native-star-rating-widget": "^1.2.0",
    "react-native-toast-message": "^2.1.8",
    "react-native-uuid": "^2.0.1",
    "react-native-webview": "^13.5.0",
    "react-native-youtube-iframe": "^2.3.0",
    "react-redux": "^8.1.3",
    "redux": "^4.2.1",
    "redux-persist": "^6.0.0",
    "tslib": "^2.3.0",
    "typesense-instantsearch-adapter": "^2.7.1"
  },
jasonbosco commented 8 months ago

Could you share your TypesenseInstantSearchAdapter initialization code?

walter-ayala commented 8 months ago

Could you share your TypesenseInstantSearchAdapter initialization code?

That's my custom hook:

import Config from 'react-native-config';
import TypesenseInstantSearchAdapter from 'typesense-instantsearch-adapter';

export const useSearchVehicle = () => {
 const typesenseInstantsearchAdapter = useMemo(() => {
    return new TypesenseInstantSearchAdapter({
      server: {
        nodes: [
          {
            host: Config.TYPESENSE_HOST,  // For Typesense Cloud use xxx.a1.typesense.net
            port: 8108, // For Typesense Cloud use 443
            protocol: 'http', // For Typesense Cloud use https
          },
        ],
        apiKey: Config.TYPESENSE_API_KEY,
        connectionTimeoutSeconds: 500,
      },
      // The following parameters are directly passed to Typesense's search API endpoint.
      //  So you can pass any parameters supported by the search endpoint below.
      //  query_by is required.
      additionalSearchParameters: {
        sort_by: 'typeRecord:asc',
        query_by: 'brandName,modelName',
        filter_by: 'typeRecord:=MODEL',
      },
    });
  }, [Config])

  const searchClient = typesenseInstantsearchAdapter.searchClient;

  return {searchClient}
};

And that is my component to search and show the hits:

/* eslint-disable react/no-unstable-nested-components */
import React, {useEffect, useRef, useState} from 'react';
import {
  FlatList,
  Image,
  Keyboard,
  TouchableOpacity,
  View,
} from 'react-native';
import {Portal} from 'react-native-portalize';
import {Modalize} from 'react-native-modalize';
import Text from "../../../common/components/Text/Text";
import Input from '../../../common/components/Inputs/Input';
import {t} from 'i18next';
import {IModelCarModal} from '../../../vehicles/models/model';
import {IconSearch} from '../../../common/components/Icons/IconSearch';
import {height, resize} from '../../../common/styles/helpers';
import {COLORS} from '../../../common/styles/constants/colors';
import emptyResult from '../../assets/EmptyResult.png';
import style from '../styles';
import {IconRightArrow} from '../../../common/components/Icons/IconRigthArrow';
import useMixpanel from '../../../common/hooks/useMixpanel';
import useMixpanel from '../../../common/hooks/useSearchVehicle';
import {
  InstantSearch,
  useInfiniteHits,
  useSearchBox,
} from 'react-instantsearch-core';
import Config from 'react-native-config';

const SearchVehicleModal: React.FC<IModelCarModal> = props => {
  const modalizeRef = useRef<Modalize>(null);
  const currentStyle = style();
  const [keyboardOffset, setKeyboardOffset] = useState(0);
  const [timer, setTimer] = useState<NodeJS.Timeout | null>(null);
  const {searchClient}=useSearchVehicle();

  const onOpen = () => {
    modalizeRef.current?.open();
  };

  useEffect(() => {
    setTimeout(() => {
      if (modalizeRef.current) {
        onOpen();
      }
    }, 200);
  }, [modalizeRef.current]);

  useEffect(() => {
    return () => {
      if (timer) {
        clearTimeout(timer);
      }
    };
  }, [timer]);

  const {track} = useMixpanel();

  useEffect(() => {
    const showSubscription = Keyboard.addListener(
      'keyboardDidShow',
      onKeyboardShow,
    );
    const hideSubscription = Keyboard.addListener(
      'keyboardDidHide',
      onKeyboardHide,
    );

    return () => {
      showSubscription.remove();
      hideSubscription.remove();
    };
  }, []);

  const onKeyboardShow = event => {
    if (props.carModel.trim() == '') {
      setKeyboardOffset(event.endCoordinates.height);
    }
  };

  const onKeyboardHide = () => {
    setKeyboardOffset(0);
  };

  const handleInputChange = (model: string) => {
    props.onChange(model);

    if (timer) {
      clearTimeout(timer);
    }

    const newTimer = setTimeout(() => {
      track('Input Value Entered', {value: model});
    }, 2000);

    setTimer(newTimer);
  };

  return (
    <Portal>
      <InstantSearch
        searchClient={searchClient}
        future={{ preserveSharedStateOnUnmount: false }}
        indexName={Config.TYPESENSE_INDEX_NAME}>
        <Modalize
          ref={modalizeRef}
          // panGestureEnabled={props.carModel.trim() !== ''}
          // alwaysOpen={125}
          panGestureEnabled={false}
          adjustToContentHeight
          withOverlay={false}
          // alwaysOpen={props.carModel.trim() !== '' ? height / 1.1 : 125}
          handlePosition="inside"
          closeOnOverlayTap={false}
          modalStyle={[
            {
              borderWidth: resize(1),
              borderTopStartRadius: resize(24),
              borderTopEndRadius: resize(24),
              borderColor: COLORS.GREY,
            },
            // Platform.OS === 'ios' && {
            //   bottom: props.carModel.trim() !== '' ? keyboardOffset : undefined,
            // },
          ]}
          FooterComponent={() => {
            if (props.carModel.trim() === '') {
              return null;
            }

            return (
              <TouchableOpacity
                onPress={props.onAddManually}
                style={currentStyle.footerModalContainer}>
                <Text style={currentStyle.footerButtonModal}>
                  {t('add_car:model_car_modal:add_manually')}
                </Text>
              </TouchableOpacity>
            );
          }}
          HeaderComponent={<SearchBox handleInput={handleInputChange} />}
          >
          {props.carModel.trim() !== '' ? (
            <InfiniteHits
              onPress={props.onPress}
              isKeyboardShow={keyboardOffset !== 0}
            />
          ) : null}
        </Modalize>
      </InstantSearch>
    </Portal>
  );
};

function InfiniteHits({...props}) {
  const {hits, isLastPage, showMore} = useInfiniteHits();
  const currentStyle = style();

  return (
    <FlatList
      data={hits}
      style={{height: props.isKeyboardShow ? height / 4 : height / 1.6}}
      keyExtractor={item => item.objectID}
      onEndReached={() => {
        if (!isLastPage) {
          showMore();
        }
      }}
      ListHeaderComponent={
        <>
          {hits.length !== 0 && (
            <Text style={currentStyle.subtitleModal}>
              {t('add_car:model_car_modal:results')}
            </Text>
          )}
        </>
      }
      ListEmptyComponent={
        <View style={currentStyle.emptyModalContainer}>
          <>
            <Image source={emptyResult} style={currentStyle.emptyImageModal} />
            <Text style={currentStyle.withoutResultsText}>Sin resultados</Text>
            <Text style={currentStyle.tryAgainText}>
              {t('add_car:model_car_modal:try_search')}
            </Text>
          </>
        </View>
      }
      renderItem={({item}) => (
        <TouchableOpacity
          style={currentStyle.itemModalContainer}
          key={item.objectID}
          activeOpacity={0.6}
          onPress={() =>
            props.onPress({
              brandID: item.brandID,
              brandName: item.brandName,
              modelName: item.modelName,
              objectID: item.objectID,
              availableYears: item.availableYears,
            })
          }>
          <Text style={currentStyle.modelCarItem}>
            {`${item.brandName} ${item.modelName}`}
          </Text>
          <IconRightArrow color="#000" size={0} />
        </TouchableOpacity>
      )}
    />
  );
}

function SearchBox(props) {
  const {query, refine} = useSearchBox();
  const currentStyle = style();
  const [inputValue, setInputValue] = useState(query);
  const inputRef = useRef(null);

  useEffect(() => {
    const loadingTimeout = setTimeout(() => {
      props.handleInput(inputValue);
      refine(inputValue);
    }, 500);

    return () => {
      if (loadingTimeout) {
        clearTimeout(loadingTimeout);
      }
    };
  }, [inputValue]);

  function setQuery(newQuery: string) {
    setInputValue(newQuery);
    // props.handleInput(newQuery);
    // refine(newQuery);
  }

  // Track when the InstantSearch query changes to synchronize it with
  // the React state.
  // We bypass the state update if the input is focused to avoid concurrent
  // updates when typing.
  // if (query !== inputValue && !inputRef.current?.isFocused()) {
  //   setInputValue(query);
  // }

  return (
    <View style={[currentStyle.headerModalContainer]}>
      <Input
        rightIcon={
          <View>
            <IconSearch />
          </View>
        }
        refs={inputRef}
        title={'Busca tu vehículo'}
        value={inputValue}
        onChangeText={setQuery}
        placeholder={'Buscar'}
        clearButtonMode="while-editing"
        autoCapitalize="none"
        autoCorrect={false}
        spellCheck={false}
      />
    </View>
  );
}

export default SearchVehicleModal;

In iOS everything works, the problem is in android