skip to Main Content

I’m learning react-native/expo and I’m stuck on this problem for hours.

So when I type anything, the whole page re-renders. I know that because in the code I upload an image and in console I see the image uri. So when I type, I see it again and again until the app crashes.

Jsfiddle code

I uploaded here the whole code because it’s too long.

The flow is – Fill the form – upload image if needed – press the confirm button

I tried using useCallback, but with no success.

MRE as requested

import React, { useState, useCallback } from 'react';
import { View, TextInput, Text, StyleSheet, TouchableOpacity, Image  } from 'react-native';
import KeyboardWrapper from '../KeyboardWrapper';
import * as ImagePicker from 'expo-image-picker';

const FormComponent = ({ onSave }) => {
  const [formData, setFormData] = useState({
    driverName1: '',
      });
const [images, setImages] = useState([]); // State to store captured images

// Function to handle image capture
  const handleCaptureImage = useCallback(async () => {
    const { status } = await ImagePicker.requestCameraPermissionsAsync();
    if (status !== 'granted') {
      alert('Camera permission is required to take pictures.');
      return;
    }
  
    try {
      const result = await ImagePicker.launchCameraAsync({
        mediaTypes: ImagePicker.MediaTypeOptions.Images,
        allowsEditing: false,
        aspect: [16, 9],
        quality: 1,
      });

      if (!result.cancelled && result.assets.length > 0) {
        // Add the new image URI to the images state
        setImages(prevImages => [...prevImages, result.assets[0].uri]);
      }
    } catch (error) {
      console.error('Error capturing image:', error);
    }
  }, []);

  const handleChange = useCallback((name, value) => {
    setFormData({ ...formData, [name]: value });
  }, [formData]);
 return (
    <KeyboardWrapper>
    <View style={styles.container}>
      <View style={styles.form}>
      <TextInput
        placeholder="Sofer 1"
        value={formData.driverName1}
        onChangeText={(text) => handleChange('driverName1', text)}
        style={styles.textInput}
      />
     {/* Display captured images */}
      <View style={styles.formImgs}>
        {images.map((imageUri, index) => (
          <View key={index} style={styles.img}>
              {console.log('Image URI:', imageUri)}
            <Image
              source={{ uri: imageUri }}
              style={{ width: 100, height: 100 }}
            />
          </View>
        ))}
      </View>
      <View style={styles.formAction}>
            <TouchableOpacity onPress={handleCaptureImage}>
            <View style={styles.btn}>
              <Text style={styles.btnText}>Scaneaza</Text>
            </View>
          </TouchableOpacity>
          </View>
      </View>
    </View>
    </KeyboardWrapper>
  );
};

Short video of the problem using MRE

2

Answers


  1. the problem is with handlechange it should be like this

      const handleChange = useCallback((name, event ) => {
        setFormData({ ...formData, [name]: event.target.value});
      }, [formData]);
    
    • check if getting name params in handleChange , if not then event also have name params inside it
    Login or Signup to reply.
  2. I believe the component re-rendering is a default feature of React. If you want more granular control over when your component re-renders, you’re probably looking for React.memo. Wrapping a component in this will prevent it from rerendering by default.

    const { useState, memo } = React;
    
    const ImgDisplay = memo(({ imgs }) => {
      console.log("rendering ImgDisplay");
      return (
        <div>
          {
            imgs.map((img, i) => <div key={i}>{img}</div>)
          }
        </div>
      )
    })
    
    const Example = () => {
      const [images, setImages] = useState([]);
      const [txt, setTxt] = useState("");
    
      const captureImage = () => {
        const randomNumberForImagePlaceholder = Math.floor(Math.random() * 100);
        setImages(imgs => [...imgs, randomNumberForImagePlaceholder]);
      };
    
      return (
        <div>
          <input 
            type="text"
            onChange={e => setTxt(e.target.value)}
          />
          <ImgDisplay imgs={images} />
          <button onClick={captureImage}>add image</button>
        </div>
      )
    };
    
    ReactDOM.createRoot(
      document.getElementById("root")
    ).render(
      <Example />
    );
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>

    And the same demo, but without memo so you can see the log happening when input changes.

    const { useState, memo } = React;
    
    const ImgDisplay = ({ imgs }) => {
      console.log("rendering ImgDisplay");
      return (
        <div>
          {
            imgs.map((img, i) => <div key={i}>{img}</div>)
          }
        </div>
      )
    };
    
    const Example = () => {
      const [images, setImages] = useState([]);
      const [txt, setTxt] = useState("");
    
      const captureImage = () => {
        const randomNumberForImagePlaceholder = Math.floor(Math.random() * 100);
        setImages(imgs => [...imgs, randomNumberForImagePlaceholder]);
      };
    
      return (
        <div>
          <input 
            type="text"
            onChange={e => setTxt(e.target.value)}
          />
          <ImgDisplay imgs={images} />
          <button onClick={captureImage}>add image</button>
        </div>
      )
    };
    
    ReactDOM.createRoot(
      document.getElementById("root")
    ).render(
      <Example />
    );
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search