0

I have written hooks for handling multiple image selection on my expo application.

import { useCallback, useState } from 'react';
import { CameraRoll } from 'react-native';

export function useCameraRoll({
  first = 40,
  assetType = 'Photos',
  groupTypes = 'All',
}) {
  const [photos, setPhotos] = useState([]);
  const [after, setAfter] = useState(null);
  const [hasNextPage, setHasNextPage] = useState(true);

  const getPhotos = useCallback(async () => {
    if (!hasNextPage) return;

    const { edges, page_info: pageInfo } = await CameraRoll.getPhotos({
      first,
      assetType,
      groupTypes,
      ...(after && { after }),
    });

    if (after === pageInfo.end_cursor) return;

    const images = edges.map(i => i.node).map(i => i.image);

    setPhotos([...photos, ...images]);
    setAfter(pageInfo.end_cursor);
    setHasNextPage(pageInfo.has_next_page);
  }, [after, hasNextPage, photos]);

  return [photos, getPhotos];
}

And I'm referencing the hooks function on another component like so:

import { useCameraRoll } from '../shared/hooks';

 const _pickImage = async () => {
    const [photos, getPhotos] = await useCameraRoll({first: 80})
  };

And initiating the function to run onPress:

<TouchableOpacity style={styles.button} onPress={_pickImage}>
        <Text style={{ color: 'white' }}>Add Photos</Text>
      </TouchableOpacity>

However I'm getting: Invalid hook call. Hooks can only be called inside of the body of a function component.

Am I missing something? Please advise.

Thank you.

Updated code:

        const RenderImagePicker = ({
  // eslint-disable-next-line no-unused-vars
  setFieldValue,
  name,
  value,
  label,
  meta: { touched, error },
}) => {
  const [uploading, setUploading] = useState(false);
  function MyComponent() {
    const [photos, getPhotos] = useCameraRoll({ first: 80 });
  }
  return (
    <TouchableOpacity style={styles.button} onPress={MyComponent}>
      <Text style={{ color: "white" }}>Add Photos</Text>
    </TouchableOpacity>
  );
};


  const _handleImagePicked = async (pickerResult) => {
    try {
      setUploading(true);
      if (!pickerResult.cancelled) {
        const uploadResponse = await uploadImageAsync(pickerResult.uri);
        const uploadResult = await uploadResponse.json();
        setFieldValue(name, uploadResult.location);
      }
    } catch (e) {
      console.log(e);
      // eslint-disable-next-line no-undef
      alert('Upload failed, sorry :(');
    } finally {
      setUploading(false);
    }
    async function uploadImageAsync(uri) {
      const apiUrl = 'http://xx:3000/upload';
      const uriParts = uri.split('.');
      const fileType = uriParts[uriParts.length - 1];

      // eslint-disable-next-line no-undef
      const formData = new FormData();
      formData.append('photo', {
        uri,
        name: `photo.${fileType}`,
        type: `image/${fileType}`,
      });

      const options = {
        method: 'POST',
        body: formData,
        headers: {
          Accept: 'application/json',
          'Content-Type': 'multipart/form-data',
        },
      };
      console.log(options);
      return fetch(apiUrl, options);
    }
  };



  const _maybeRenderUploadingOverlay = () => {
    if (uploading) {
      return (
        <View>
          <ActivityIndicator
            color="#ff0000"
            size="large"
            style={{ alignSelf: 'center', flex: 1 }}
          />
        </View>
      );
    }
  };


  function MyComponent() {
    const [photos, getPhotos] = useCameraRoll({first: 80});
    return (
      <>
        <TouchableOpacity style={styles.button} onPress={getPhotos}>    
        <Text style={{ color: 'white' }}>Add Photos</Text>  
      </TouchableOpacity>
        {!!error && <Text style={{ color: 'red' }}>{error}</Text>}
        {_maybeRenderUploadingOverlay()}
      </>
    );
  }
};

1 Answer 1

1

As clearly mentioned in the Rules of Hooks, Hooks can only be called at the top level of the functional component. Not inside loops, nested functions, conditions which also include other hooks body. You can fix your code as shown:

import { useCameraRoll } from '../shared/hooks';

function MyComponent() {
    const [photos, getPhotos] = await useCameraRoll({first: 80});

    return (
      <TouchableOpacity style={styles.button} onPress={getPhotos}>
        <Text style={{ color: 'white' }}>Add Photos</Text>
      </TouchableOpacity>
     );
}
Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for the comment, however that returns another error: RenderImagePicker(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null. I have updated main post with updated code.
Can you share this function {_maybeRenderUploadingOverlay() and if possible the RenderImagePicker code.
Error is in the RenderImagePicker, should return JSX or null. Currently, it returns undefined
Updated RenderImagePicker code, did you mean something like this? If so, the issue persist as invalid hook call, as mentioned previously.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.