skip to Main Content

I have two reusable TextInputs, namely, AppTextInput and a reusable button, namely, AppButton.

Here are their codes:

AppButton.js

const AppButton = ({
    title = '',
    iconName = '',
    onPress,
    mode = 'contained',
    ...props
}) => {

    console.log("Button rendered")

    return (
        <Button
            icon={iconName}
            mode={mode}
            onPress={onPress}
            {...props}
        >
            {title}
        </Button>
    )
}

Here is for the AppTextInput.js

const AppTextInput = ({
    inputStyle,
    outlineStyle,
    leftIcon = '',
    rightIcon = '',
    right,
    onRightIconPress,
    label = '',
    ...props
}) => {

    console.log([label, " TextInput Rendered"])

    return (
        <TextInput
            label={label}
            mode='outlined'
            style={[styles.inputStyle, inputStyle]}
            outlineStyle={[styles.outlineStyle, outlineStyle]}
            left={
                leftIcon ? (
                    <TextInput.Icon
                        icon={leftIcon}
                        disabled
                    />
                ) : null
            }
            right={
                rightIcon ? (
                    <TextInput.Icon
                        icon={rightIcon}
                        onPress={onRightIconPress}
                    />
                ) : (
                    right
                )
            }
            {...props}
        />
    )
}

I added console.log()s on the two to determine if they re-renders or not.

Now here’s my problem:

I have a simple form that consists of fields of CurrentPassword and NewPassword.

Whenever I change the state for the icon of the CurrentPassword field, it also renders the AppButton and the NewPassword TextInput.

How do I prevent the re-render?

Below is sample image and code for the parent component:

enter image description here

ChangePasswordScreen.js

const ChangePasswordScreen = () => {

    const [currentPasswordSecureText, setCurrentPasswordSecureText] = useState(true);
    const [newPasswordSecureText, setNewPasswordSecureText] = useState(true);

    const [currentPasswordIcon, setCurrentPasswordIcon] = useState("eye-off-outline");
    const [newPasswordIcon, setNewPasswordIcon] = useState("eye-off-outline");

    const showHideCurrentPassword = () => {

        setCurrentPasswordSecureText(!currentPasswordSecureText)

        currentPasswordIcon === "eye-off-outline" ?
            setCurrentPasswordIcon("eye") :
            setCurrentPasswordIcon("eye-off-outline");
    }

    const showHideNewPassword = () => {
        setNewPasswordSecureText(!newPasswordSecureText)

        newPasswordIcon === "eye-off-outline" ?
            setNewPasswordIcon("eye") :
            setNewPasswordIcon("eye-off-outline");
    }

    return (
        <ScrollView
            style={styles.container}
            keyboardShouldPersistTaps='handled'
        >

            <AppTextInput
                label='Current Password'
                autoCapitalize="none"
                autoCorrect={false}
                inputStyle={styles.input}
                leftIcon='lock'
                rightIcon={currentPasswordIcon}
                secureTextEntry={currentPasswordSecureText}
                onRightIconPress={showHideCurrentPassword}
            />

            <AppTextInput
                label='New Password'
                autoCapitalize="none"
                autoCorrect={false}
                inputStyle={styles.input}
                leftIcon='lock'
                rightIcon={newPasswordIcon}
                secureTextEntry={newPasswordSecureText}
                onRightIconPress={showHideNewPassword}
            />

            <AppButton
                title="Update"
                style={styles.button}
            />

        </ScrollView>
    )
}

This is the result of the log whenever I press the eye icon.

enter image description here

I have tried useCallback and useMemo but I don’t quite get it as I am still unfamiliar with the two.

Thank you!

3

Answers


  1. const name = useRef('');
    
    <TextInput
        ref={name}
        onChangeText={text => (name.current.value = text)}
        placeholder={'Name'}
        placeholderTextColor="#909090"
        style={{width: '95%', height: '100%'}}
    />
    

    Maybe this will solve your problem. Replace your useState with useRef for input.
    Please let me know if this works.

    Login or Signup to reply.
  2. That’s very common issue in react development.

    I have faced same issues earlier So, I tried below solution.

    • create custom input component with internal state management and combine use of useImperativeHandle.
    • only individual component state will and update and then it will re-rendered only.
    • using the useImperativeHandle you can simply get the input value and set the input values with ref.

    Example:

    import {View} from 'native-base';
    import React, {
      forwardRef,
      useState,
      useCallback,
      useRef,
      useImperativeHandle,
    } from 'react';
    import {TextInput, TextInputProps, Button, Keyboard} from 'react-native';
    
    // ** Input component with managing state internally
    const AppTextInput = forwardRef((props: TextInputProps, ref) => {
      const [inputValue, setInputValue] = useState(props?.defaultValue || '');
    
      const inputRef = useRef<TextInput>(null);
    
      const onChangeTextHandler = useCallback((enteredValue: string) => {
        setInputValue(enteredValue);
      }, []);
    
      const initHandler = useCallback(() => {
        return {
          getValue: () => inputValue,
          setValue: (newVal: string) => setInputValue(newVal),
          focus: () => inputRef.current?.focus?.(),
          blur: () => inputRef.current?.blur?.(),
        };
      }, [inputValue]);
    
      useImperativeHandle(ref, initHandler, [initHandler]);
    
      return (
        <TextInput
          ref={inputRef}
          keyboardType="default"
          blurOnSubmit={false}
          {...props}
          value={inputValue}
          onChangeText={onChangeTextHandler}
        />
      );
    });
    
    // ** Usage
    const App = () => {
      const emailRef = useRef(null);
      const passwordRef = useRef(null);
    
      const loginHandler = useCallback(() => {
        const emailInputVal = emailRef.current?.getValue?.();
        const passwordInputVal = passwordRef.current?.getValue?.();
        console.log('email : ', emailInputVal, ' password : ', passwordInputVal);
      }, []);
    
      return (
        <View>
          <AppTextInput
            ref={emailRef}
            onSubmitEditing={() => {
              passwordRef.current?.focus?.();
            }}
          />
          <AppTextInput
            ref={passwordRef}
            onSubmitEditing={() => {
              Keyboard.dismiss();
              loginHandler();
            }}
          />
          <Button title="Login" onPress={loginHandler} />
        </View>
      );
    };
    
    export default App;
    
    Login or Signup to reply.
  3. Although you can run a line of code that shows you button rendering..., there’s a React core functionality called the reconciliation algorithm. This part works at low level DOM manipulation and it’s responsible for determining what DOM nodes are being rendered / updated. Basically it’s a diff check between Virtual DOM and Real DOM.

    EDIT:

    As an addition to my previous comment where I try to explain in a very simple way how React works, I would say: Every change of state will trigger re-rendering. But React provide us some options to work around this issue. One of them is working with useRef hook.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search