Is it possible to Get 'style' property values from React Native Element AFTER rendering using useRef?

469 Views Asked by At

I'm trying to dynamically apply margin & padding to a View, based on a ref'd TextInput's borderRadius. I am new to React coming from Xamarin where this type of thing is common.

I'm not sure if I have the correct approach, but I have seen some examples of people deriving style values from useRef.

Here is my custom LabelInput component:

import React, {useState} from 'react';
import {
  View,
  Animated,
  StyleSheet,
  ViewProps,
} from 'react-native';

import {Colors} from '../../../resources/colors';
import Text from './Text';
import TextInput from './TextInput';
import {TextInputProps} from 'react-native/Libraries/Components/TextInput/TextInput';
import {isNullOrWhiteSpace} from '../../../utils/stringMethods';
import {TextProps} from 'react-native/Libraries/Text/Text';

interface LabeledInputProps {
  label: string;
  error: string;
  onChangeText: (text: string) => void;
  placeholder?: string;
  inputValue: string;
  mask?: (text: string) => string;
  validator?: (text: string) => string;
  onValidate?: (value: string) => void;
  viewProps?: ViewProps;
  textProps?: TextProps;
  errorTextProps?: TextProps;
  inputProps?: TextInputProps;
}

export default function LabeledInput(props: LabeledInputProps) {
  const inputRef = React.useRef<any>(null);
  const [dynamicStyle, setDynamicStyle] = useState(StyleSheet.create({
    dynamicContainer:{
        marginHorizonal: 0,
        paddingHorizonal: 0,
    }
  }));

  const changeTextHandler = (inputText: string) => {
    const displayText = props?.mask ? props.mask(inputText) : inputText;
    props.onChangeText(displayText);

    // ultimately not the exact behavior I'm after, but this is a simple example.
    var test = inputRef.current.props.style; 
    // props.style always returns undefined, 
    // there doesn't appear to be a 'props' property on the 'current' object when debugging.
    setDynamicStyle(StyleSheet.create({
        dynamicContainer:{
            marginHorizonal: test.borderRadius, // I want the padding/margin of this element to be
            paddingHorizonal: test.borderRadius,// dynamically set based on the inputRef's borderRadius
        }
      }))
  };

  return (
    <View
      {...props.viewProps}
      style={[
        props.viewProps?.style,
        localStyles.container,
      ]}>
      <TextInput
        ref={inputRef}
        {...props.inputProps}
        placeholder={props.placeholder}
        style={localStyles.input}
        onChangeText={changeTextHandler}
        value={props.inputValue}
      />
      <Animated.View
        pointerEvents={'none'}>
        <Text
          {...props.textProps}
          style={[props.textProps?.style, animatedStyles.label]}>
          {props.label}
        </Text>
      </Animated.View>
      {/* {stuff} */}
    </View>
  );
}

const localStyles = StyleSheet.create({
  container: {
    backgroundColor: 'blue',
    justifyContent: 'flex-start',
    flex: 1,
  },
  label: {
    fontWeight: 'bold',
    marginBottom: 8,
  },
  input: {
    padding: 8,
  },
  error: {
    backgroundColor: 'pink',
    fontSize: 12,
    paddingHorizontal: 8,
    color: Colors.danger,
    marginTop: 4,
  },
});

const animatedStyles = StyleSheet.create({
  label: {
    fontSize: 16,
    fontWeight: 'normal',
  },
});

Here is my custom LabelInput component with forwardRef() implemented:

import React, {ForwardedRef, forwardRef} from 'react';
import {TextInput as NativeTextInput, TextInputProps} from 'react-native';
import {useGlobalStyles} from '../../../resources/styles';

const TextInput = (
  props: TextInputProps,
  ref: ForwardedRef<NativeTextInput>,
) => {
  const styles = useGlobalStyles();
  return (
    <NativeTextInput
      {...props}
      ref={ref}
      style={[styles.textInput, props.style]}
      placeholderTextColor={styles.textInput.borderColor}
      onChangeText={(text: string) => {
        if (props.onChangeText) {
          props.onChangeText(text);
        }
      }}
    />
  );
};

export default forwardRef(TextInput);

I've tried referencing inputRef from different hooks, like useCallback & useEffect.

var test = inputRef.current.props.style; always returns undefined. And there doesn't appear to be a 'props' property on the 'current' object when debugging.

1

There are 1 best solutions below

1
Muhammad Nouman Rafique On

The link you mentioned contains two files with inputRef. Since inputRef is in parent component and use ref prop to pass inputRef, this will not work. ref is not available as prop. If you still want to use ref as prop, then use forward ref in child component as access the ref as second argument or you can use any other prop name to pass ref i.e. innerRef. You can read more in react documentation. Forward Refs

According to the code you attach in code sandbox, i think you are trying to access input styles in two components: App and LabeledInput. You should use one ref in main component and use it in LabelInput component. If you still want to have separate refs then you can ref callback function and attach the node with both refs.

const attachRef = (node: NativeTextInput) => {
    inputRef.current = node;
    ref.current = node;
};

return <TextInput ref={attachRef} />;

The correct type for inputRef.current is TextInputProps.

const inputRef = useRef() as MutableRefObject<TextInputProps>;

I have updated the code sandbox. I was able to access input field styles in both components. Hope this solves your problem.