Home > front end >  Reusing custom TextInput component in react native?
Reusing custom TextInput component in react native?

Time:12-24

I am using react native 0.68.5

When I call the renderReusableTextInput method in Main.js I am sending all the styles including the needed and unneeded styles. This can lead performance penalty.

So, is there a way that I can send the only needed styles to the renderReusableTextInput method.

I also want to know is the code reusing approach of mine correct or there are better ways.

I have the following code-logic structure:

(i) A Reusable component; It has only structure no style or state

(ii) A file that includes reusable methods

(iii) The Main component, which contains components, styles and states

ReusableTextInput.js


// import ...

const ReusableTextInput = forwardRef(({
  inputName,
  inputLabel,
  inputValue,
  secureTextEntry,
  textInputContainerWarperStyle,
  textInputContainerStyle,
  textInputLabelStyle,
  textInputStyle,
  textInputHelperStyle,
  textInputErrorStyle,
  helperText,
  inputError,
  onFocus,
  onChangeText,
  onBlur,
}, inputRef) => {

  return (
    <View style={[textInputContainerWarperStyle]}>
      <View style={[textInputContainerStyle]}>
    <Text style={[textInputLabelStyle]}>
      {inputLabel}
    </Text>
    <TextInput
      ref={(elm) => inputRef[inputName] = elm}
      style={[textInputStyle]}
      value={inputValue}
      secureTextEntry={secureTextEntry}
      onChangeText={onChangeText}
      onFocus={onFocus}
      onBlur={onBlur}
    />
      </View>
      {
        ((inputRef[inputName]) && (inputRef[inputName].isFocused()) && (!inputValue))
          ?
          <Text style={[textInputHelperStyle]}>
            {helperText}
          </Text>
          :
          null
      }
      {
        ((inputError) && (inputValue))
          ?
          <Text style={[textInputErrorStyle]}>
            {inputError}
          </Text>
          :
          null
      }
    </View>
  );
});

export default memo(ReusableTextInput);

reusableMethods.js


// import ...

const handleFocus = (state, setState, styles) => {

  const stateData = { ...state };

  stateData.styleNames.textInputContainer = {
    ...styles.textInputContainer,
    ...styles[`${stateData.name}ExtraTextInputContainer`],
    ...styles.textInputContainerFocus,
  };
  stateData.styleNames.textInputLabel = {
    ...styles.textInputLabel,
    ...styles[`${stateData.name}ExtraTextInputLabel`],
    ...styles.textInputLabelFocus,
  };
  stateData.styleNames.textInput = {
    ...styles.textInput,
    ...styles[`${stateData.name}ExtraTextInput`],
    ...styles.textInputFocus,
  };

  // other logics...

  setState(stateData);
};

const handleChangeText = (state, setState, text) => {
  const stateData = { ...state };

  // individual validation
  const schemaData = Joi.object().keys(stateData.validationObj); // I used Joi for validation
  const inputData = { [stateData.name]: text };

  const options = { abortEarly: false, errors: { label: false } };

  const result = schemaData.validate(inputData, options);
  // -----

  stateData.error = (result.error) ? result.error.details[0].message : '';

  stateData.value = text;

  // other logics...

  setState(stateData);
};

const handleBlur = (state, setState, styles) => {

  const stateData = { ...state };

  if (stateData.value) {
    stateData.styleNames.textInputContainer = {
      ...styles.textInputContainer,
      ...styles[`${stateData.name}ExtraTextInputContainer`],
      ...styles.textInputContainerFocus,
      ...styles[`${stateData.name}ExtraTextInputContainerFocus`],
      ...styles.textInputContainerBlurText,
      ...styles[`${stateData.name}ExtraTextInputContainerBlurText`],
    };
    stateData.styleNames.textInputLabel = {
      ...styles.textInputLabel,
      ...styles[`${stateData.name}ExtraTextInputLabel`],
      ...styles.textInputLabelFocus,
      ...styles[`${stateData.name}ExtraTextInputLabelFocus`],
      ...styles.textInputLabelBlurText,
      ...styles[`${stateData.name}ExtraTextInputLabelBlurText`],
    };
    stateData.styleNames.textInput = {
      ...styles.textInput,
      ...styles[`${stateData.name}ExtraTextInput`],
      ...styles.textInputFocus,
      ...styles[`${stateData.name}ExtraTextInputFocus`],
      ...styles.textInputBlurText,
      ...styles[`${stateData.name}ExtraTextInputBlurText`],
    };
  }
  else {
    stateData.styleNames.textInputContainer = { ...styles.textInputContainer, ...styles[`${stateData.name}ExtraTextInputContainer`] };
    stateData.styleNames.textInputLabel = { ...styles.textInputLabel, ...styles[`${stateData.name}ExtraTextInputLabel`] };
    stateData.styleNames.textInput = { ...styles.textInput, ...styles[`${stateData.name}ExtraTextInput`] };
  }

  // other logics...

  setState(stateData);
};


// other methods...


export const renderReusableTextInput = (
  state,
  setState,
  inputRef,
  styles, 
  // contains all the styles from Main component and here I am sending all the styles including the needed and unneeded styles. I want improvement here
) => {

  return (
    <ReusableTextInput
      inputName={state.name}
      inputLabel={state.label}
      inputValue={state.value}
      inputRef={inputRef}
      secureTextEntry={state.secureTextEntry}
      textInputContainerWarperStyle={{...styles.textInputContainerWarper, ...styles[`${state.name}ExtraTextInputContainerWarper`]}}
      textInputContainerStyle={state.styleNames.textInputContainer}
      textInputLabelStyle={state.styleNames.textInputLabel}
      textInputStyle={state.styleNames.textInput}
      textInputHelperStyle={{...styles.textInputHelper, ...styles[`${state.name}ExtraTextInputHelper`]}}
      textInputErrorStyle={{...styles.textInputError, ...styles[`${state.name}ExtraTextInputError`]}}
      helperText={state.helperText}
      inputError={state.error}
      onFocus={() => handleFocus(state, setState, styles)}
      onChangeText={(text) => handleChangeText(state, setState, text)}
      onBlur={() => handleBlur(state, setState, styles)}
    />
  );
};

Main.js


// import Joi from 'joi';
// import { joiPasswordExtendCore } from 'joi-password';

// import { renderReusableTextInput } from ''; ...


const schema =
{
  email: Joi.string().strict()
    .case("lower")
    .min(5)
    .max(30)
    .email({ minDomainSegments: 2, tlds: { allow: ["com", "net", "org"] } })
    .required(),

  countryCode: // Joi.string()...,

  phoneNumber: // Joi.string()...,

  password: // Joi.string()...,
  // ...
};

const Main = () => {

  const { width: windowWidth, height: windowHeight, scale, fontScale } = useWindowDimensions();

  const minimumWidth = (windowWidth <= windowHeight) ? windowWidth : windowHeight;

  const styles = useMemo(() => currentStyles(minimumWidth), [minimumWidth]);

  const [email, setEmail] = useState({
    name: 'email', // unchangeable
    label: 'Email', // unchangeable
    value: '',
    error: '',
    validationObj: { email: schema.email },  // unchangeable
    trailingIcons: [require('../../file/image/clear_trailing_icon.png')], // unchangeable
    helperText: 'only .com, .net and .org allowed', // unchangeable
    styleNames: {
      textInputContainer: { ...styles.textInputContainer, ...styles.emailExtraTextInputContainer },
      textInputLabel: { ...styles.textInputLabel, ...styles.emailExtraTextInputLabel },
      textInput: { ...styles.textInput, ...styles.emailExtraTextInput },
    },
  });

  const [phoneNumber, setPhoneNumber] = useState({
    // ...
  });

  const [countryCode, setCountryCode] = useState({
  });

  const [password, setPassword] = useState({
  });

  // ...
 
  const references = useRef({});


  return (
    <View style={[styles.mainContainer]}>
      {
        useMemo(() => renderReusableTextInput(email, setEmail, references.current, styles), [email, minimumWidth])
      }
    </View>
  );
}

export default memo(Main);



const styles__575 = StyleSheet.create({
  // 320 to 575
  mainContainer: {
  },
  textInputContainerWarper: {
  },
  emailExtraTextInputContainerWarper: {
  },
  countryCodeExtraTextInputContainerWarper: {
  },
  phoneNumberExtraTextInputContainerWarper: {
  },
  passwordExtraTextInputContainerWarper: {
  },
  textInputContainer: {
  },
  emailExtraTextInputContainer: {
  },
  textInputContainerFocus: {
  },
  textInputContainerBlurText: {
  },
  textInputLabel: {
  },
  emailExtraTextInputLabel: {
  },
  textInputLabelFocus: {
  },
  textInputLabelBlurText: {
  },
  textInput: {
  },
  emailExtraTextInput: {
  },
  textInputFocus: {
  },
  textInputBlurText: {
  },
  textInputHelper: {
  },
  emailExtraTextInputHelper: {
  },
  textInputError: {
  },
  emailExtraTextInputError: {
  },

  // other styles...
});

const styles_576_767 = StyleSheet.create({
  // 576 to 767 
});

const styles_768_ = StyleSheet.create({
  // 768; goes to 1024;
});

const currentStyles = (width, stylesInitial = { ...styles__575 }, styles576 = { ...styles_576_767 }, styles768 = { ...styles_768_ }) => {
  let styles = {};

  if (width < 576) {
    // ...
  }
  else if ((width >= 576) && (width < 768)) {
    // ...
  }
  else if (width >= 768) {
    // ...
  }

  return styles;
};

I tried mentioned approach and want a better answer.

CodePudding user response:

First, I think you want global styles, so that you can access it from anywhere and you can also be able to execute needed code.

Make sure you use useMemo, useCallback in right manner, for better performance.

Move all Schema and Styles and Methods of your screens inside the reusableMethods.js file (at most case). It will act like the controller of all screens of your app, also make a demo method which return a tiny component and this method execute another method, so that you can get styles for different dimentions(see below code)

Store only changeable and needed properties in state variables.

Is code reusing approach of yours, correct? I can't say about that. I will say that it depends on developer choice.

you can try like below:

reusableMethods.js


// import all schema, style variants, utility methods and other

let screenStyles = {};

const executeDimensionBasedMethods = (width, screenName) => {
  if (screenName === 'a_screen_name') screenStyles[screenName] = currentStyles(width, otherParameter);
  // else if()
  // ...
};

export const renderDimension = (width, screenName) => {
  executeDimensionBasedMethods(width, screenName);
  return (
    <Text style={{ width: 0, height: 0 }}></Text>
  );
};

// all method definitions and logics

export const renderReusableTextInput = (
  state,
  setState,
  inputRef,
  screenName
) => {
  return (
    <ReusableTextInput 
      inputName={state.name}
      inputLabel={state.label}
      inputValue={state.value}
      inputRef={inputRef}
      secureTextEntry={state.secureTextEntry}
      textInputContainerWarperStyle={{ ...screenStyles[screenName].textInputContainerWarper, ...screenStyles[screenName][`${state.name}ExtraTextInputContainerWarper`] }}
      textInputContainerStyle={state.styleNames.textInputContainer || { ...screenStyles[screenName].textInputContainer }
      textInputLabelStyle={state.styleNames.textInputLabel || { ...screenStyles[screenName].textInputLabel }
      textInputStyle={state.styleNames.textInput || { ...screenStyles[screenName].textInput }
      textInputHelperStyle={{ ...screenStyles[screenName].textInputHelper, ...screenStyles[screenName][`${state.name}ExtraTextInputHelper`] }}
      textInputErrorStyle={{ ...screenStyles[screenName].textInputError, ...screenStyles[screenName][`${state.name}ExtraTextInputError`] }}
      helperText={state.helperText}
      inputError={state.error}
      onFocus={() => handleFocus(state, setState, screenStyles[screenName])}
      onChangeText={(text) => handleChangeText(state, setState, text)}
      onBlur={() => handleBlur(state, setState, screenStyles[screenName])}
    />
  );
};

Main.js


const Main = () => {
  const { width: windowWidth, height: windowHeight} = useWindowDimensions();

  const minimumWidth = (windowWidth <= windowHeight) ? windowWidth : windowHeight;

  const [email, setEmail] = useState({
    name: 'email', // unchangeable
    label: 'Email', // unchangeable
    value: '',
    error: '',
    trailingIcons: [require('../../file/image/clear_trailing_icon.png')], // unchangeable
    helperText: 'only .com, .net and .org allowed', // unchangeable
    styleNames: {
      textInputContainer: undefined,
      textInputLabel: undefined,
      textInput: undefined,
    },
  });

  // other states 

  const references = useRef({});


  return (
    <>
      {
        useMemo(() => renderDimension(minimumWidth), [minimumWidth])
      }
      <View style={{ // inline style}}>
        {
          useMemo(() => renderReusableTextInput(email, setEmail, references.current, 'nameofscreen'), [email, minimumWidth])
        }
      </View>
  </>
}

export default memo(Main);
  • Related