user1816631
user1816631

Reputation:

Focus style for TextInput in react-native

In React Native, how do you change the style of a textInput when it gets focus? Say I have something like

class MyInput extends Component {
    render () {
        return <TextInput style={styles.textInput} />;
    }
};

const stylesObj = {
    textInput: {
        height: 50,
        fontSize: 15,
        backgroundColor: 'yellow',
        color: 'black',
    }
};
const styles = StyleSheet.create(stylesObj);

And I want to change the background color on focus to green.

This documentation leads me to believe that the solution is something like

class MyInput extends Component {
    constructor (props) {
        super(props);
        this.state = {hasFocus: false};
    }

    render () {
        return (<TextInput
            style={this.state.hasFocus ? styles.focusedTextInput : styles.textInput}
            onFocus={this.setFocus.bind(this, true)}
            onBlur={this.setFocus.bind(this, false)}
        />);
    }

    setFocus (hasFocus) {
        this.setState({hasFocus});
    }
};

const stylesObj = {
    textInput: {
        height: 50,
        fontSize: 15,
        backgroundColor: 'yellow',
        color: 'black',
    }
};
const styles = StyleSheet.create({
    ...stylesObj,
    focusedTextInput: {
        ...stylesObj,
        backgroundColor: 'green',
    }
});

Ignoring potential mistakes in the styles structuring, would this be considered correct way to handle it? It seems very verbose to me.

Upvotes: 53

Views: 147061

Answers (10)

user18309290
user18309290

Reputation: 8310

Add the state callback function to style property. The approach is inspired by Pressable.

export interface TextInputStateCallbackType {
  readonly focused: boolean;
}

export type TextInputProps = Omit<TextInputPropsNative, 'style'> & {
  style?:
    | StyleProp<TextStyle>
    | ((state: TextInputStateCallbackType) => StyleProp<TextStyle>)
    | undefined;
};

export const TextInput = ({ style, ...props }: TextInputProps) => {
  const [focused, setFocused] = useState(false);
  return (
    <TextInputNative
      style={typeof style === 'function' ? style({ focused }) : style}
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
      {...props}
    />
  );
};
<TextInput
  style={({ focused }) => {
    return [
      styles.input,
      {
        borderColor: focused ? 'blue' : 'black',
      },
    ];
  }}
  onChangeText={onChangeText}
  value={text}
/>

Upvotes: 0

Nitin Kumar
Nitin Kumar

Reputation: 1

set initial value in function component

const [warnColor, setWrnColor] = useState("grey");

set this in text input

style={[styles.brdColor, { borderColor: warnColor }]}

set this in stylesheet

 brdColor: {
    height: 40,
    borderColor: "grey",
    borderBottomWidth: 1,
    marginTop: heightToDp("2%"),
    width: "100%",
  }

Upvotes: 0

Lianel
Lianel

Reputation: 159

For that propose I design this simple logic in functional component (it works the same in class components with the pertinents changes), it can be apply to several <textinputs/>. Below I leave an example:

// state
     const [isFocused, setIsFocused] = useState({
       name: false,
       email: false,
       phone: false,
     })
// handlers
     const handleInputFocus = (textinput) => {
       setIsFocused({
         [textinput]: true
       })
     }
     const handleInputBlur = (textinput) => {
       setIsFocused({
         [textinput]: false
       })
     }
// JSX

     <View style={styles.form} >
        <TextInput
          style={isFocused.name ? [styles.inputs, { borderColor: 'blue' }] : styles.inputs}
          placeholder='Name'
          placeholderTextColor={darkColors.text}
          textContentType='name'
          keyboardType='default'
          onFocus={() => handleInputFocus('name')}
          onBlur={() => handleInputBlur('name')}
        />
        <TextInput
          style={isFocused.email ? [styles.inputs, { borderColor: 'blue' }] : styles.inputs}
          placeholder='Email'
          placeholderTextColor={darkColors.text}
          textContentType='emailAddress'
          keyboardType='email-address'
          onFocus={() => handleInputFocus('email')}
          onBlur={() => handleInputBlur('email')}
        />
        <TextInput
          style={isFocused.phone ? [styles.inputs, { borderColor: 'blue' }] : styles.inputs}
          placeholder='Phone'
          placeholderTextColor={darkColors.text}
          keyboardType='phone-pad'
          onFocus={() => handleInputFocus('phone')}
          onBlur={() => handleInputBlur('phone')}
        />
      </View>

Upvotes: 3

Ajay Sivan
Ajay Sivan

Reputation: 2989

You can create a state to keep track of the input-state and use that state to toggle the style. Here is a simple example

const App = () => {
  const [isActive, setActive] = useState(false);

  return (
    <TextInput style={{ color: isActive ? 'black' : 'grey' }} onFocus={() => setActive(true)} onBlur={() => setActive(false)}/>
  );
}

Upvotes: 5

Gabriel P.
Gabriel P.

Reputation: 3560

The best way to control the style when the element is focused / blurred is to create your own TextInput wrapper

export const MyAppTextInput = (props) => {
  return (
    <TextInput
      {...props}
    />
  );
};

Note that {...props} will pass in any property you already set or available for TextInput.

Then extend the above component by adding state to apply styles when focus / blur

export const MyAppTextInput = (props) => {
  const [isFocused, setIsFocused] = useState(false);
  return (
    <TextInput
      {...props}
      style={[props.style, isFocused && {borderWidth: 5, borderColor: 'blue'}]}
      onBlur={() => setIsFocused(false)}
      onFocus={() => setIsFocused(true)}
    />
  );
};

And remember when you use the component to bind the value like in the example (see value={passwordText}); otherwise the value will disappear on blur as a new render commences after the state change.

<MyAppTextInput
          style={styles.input}
          value={passwordText}
          textContentType="password"
          autoCompleteType="off"
          secureTextEntry
          onChangeText={text => {
            setPasswordText(text);
          }}
        />

You can of course avoid creating a wrapper but if you have more than one input it will create a mess in your input(s) parent components as you will have to add repeating logic.

Upvotes: 16

colemerrick
colemerrick

Reputation: 1218

Use refs, DirectManipulation and setNativeProps for more performance: https://facebook.github.io/react-native/docs/direct-manipulation.

class MyInput extends Component {
  focusedInput = () => { 
    this.textInput.setNativeProps({
      style: { backgroundColor: 'green' }
    }) 
  }

  blurredInput = () => { 
    this.textInput.setNativeProps({
      style: { backgroundColor: 'yellow' }
    }) 
  }

  render () {
      return <TextInput 
                ref={c => { this.textInput = c}} 
                style={styles.textInput}
                onFocus={this.focusedInput}
                onBlur={this.blurredInput} />
  }

}

const stylesObj = { textInput: { height: 50, fontSize: 15, backgroundColor: 'yellow', color: 'black', } }

const styles = StyleSheet.create(stylesObj)

This updates the TextInput component directly without re-rendering the component hierarchy.

Upvotes: 9

patel jigar
patel jigar

Reputation: 52

 <TextInput
 style={{ backgroundColor: 'white', height: 40, width: 100, alignItems: 'center' 
   }} 
 theme={{ colors: { placeholder: 'white', text: 'white', primary: 'white', 
  underlineColor: 'transparent', background: '#003489' } }}
   />

Upvotes: 0

Rip3rs
Rip3rs

Reputation: 1540

Hey guys I kinda used everyones idea :p

@Felix gave me an idea that might be perhaps even cleaner. (I would have loved to not have included state though on this static component, just to change styling... But I am to new to this to figure that out.

here was my solution:

import React, { Component } from 'react';
import { StyleSheet, TextInput } from 'react-native';

class TxtInput extends Component {
  constructor(props) {
    super(props);
    this.state = {
      style: {},
    };
  }

  onFocus = () => {
    const state = { ...this.state };
    state.style = {
      borderStyle: 'solid',
      borderColor: '#e74712',
    };

    this.setState(state);
  }

  onBlur = () => {
    console.log('on ONBLUR')
    const state = { ...this.state };
    state.style = {};

    this.setState(state);
  }

  render = () => <TextInput style={[styles.input, this.state.style]} onFocus={() => this.onFocus()} onBlur={() => this.onBlur()} />;
}

const styles = StyleSheet.create({
  input: {
    color: '#000000',
    fontFamily: 'MuseoSans 700 Italic',
    fontSize: 22,
    borderRadius: 34,
    borderStyle: 'solid',
    borderColor: 'transparent',
    borderWidth: 5,
    backgroundColor: '#ffffff',
    textAlign: 'center',
    width: '25%',
 },
});

export default TxtInput;

I added the style into an array, have all the actual input styling done on the first property of the array and the second one the nit picking of the focus and blue.

Hope it helps

Upvotes: 2

Nader Dabit´s pointed me to do something similar -- using the state for styles -- but I think it can be done in a cleaner way if you created separate styles for the focused and unfocused style and pass only the style ID as follows:

getInitialState() {
  return { style: styles.textinput_unfocused }
}
onFocus() {
  this.setState({ style: styles.textinput_focused })
}
onBlur() {
  this.setState({ style: styles.textinput_unfocused })
}

in render -- referencing by styleID in this.state.style, note how the different styles are passed as an Array:

<TextInput 
  onBlur={ () => this.onBlur() }
  onFocus={ () => this.onFocus() }
  style={ [styles.textinput, this.state.style] }  />

+ your stylesheet à la carte:

textinput_focused: {
  backgroundColor: 'red',
  color: 'white'
}
textinput_unfocused: {
  backgroundColor: 'green'
}

Upvotes: 2

Nader Dabit
Nader Dabit

Reputation: 53691

You can achieve this by passing in the onFocus and onBlur events to set and unset styles when focused and blurred:

  onFocus() {
    this.setState({
        backgroundColor: 'green'
    })
  },

  onBlur() {
    this.setState({
      backgroundColor: '#ededed'
    })
  },

And then, in the TextInput do this:

<TextInput 
    onBlur={ () => this.onBlur() }
    onFocus={ () => this.onFocus() }
    style={{ height:60, backgroundColor: this.state.backgroundColor, color: this.state.color }}  />

I've set up a full working project here. I hope this helps!

https://rnplay.org/apps/hYrKmQ

'use strict';

var React = require('react-native');
var {
  AppRegistry,
  StyleSheet,
  Text,
  View,
  TextInput
} = React;

var SampleApp = React.createClass({

  getInitialState() {
    return {
        backgroundColor: '#ededed',
      color: 'white'
    }
  },

  onFocus() {
        this.setState({
        backgroundColor: 'green'
    })
  },

  onBlur() {
    this.setState({
      backgroundColor: '#ededed'
    })
  },

  render: function() {
    return (
      <View style={styles.container}>
       <TextInput 
        onBlur={ () => this.onBlur() }
        onFocus={ () => this.onFocus() }
        style={{ height:60, backgroundColor: this.state.backgroundColor, color: this.state.color }}  />
      </View>
    );
  }
});

var styles = StyleSheet.create({
  container: {
    flex: 1,
    marginTop:60
  }
});

AppRegistry.registerComponent('SampleApp', () => SampleApp);

Upvotes: 78

Related Questions