In order to make a custom input, I had to create an InputWrapper to apply common style and add some animation behavior:
import React, { ReactElement, useState, useEffect } from 'react';
import {
StyleSheet, View, TouchableWithoutFeedbackProps,
} from 'react-native';
import Text from './Text';
import { Colors, Theme } from '../constants';
import { TouchableWithoutFeedback } from 'react-native-gesture-handler';
interface Props extends TouchableWithoutFeedbackProps {
label: string;
value?: any;
children: any;
focus: boolean;
}
const styles = StyleSheet.create({
// ...
});
const InputWrapper = ({
label, value, children, focus, ...props
}: Props): ReactElement => {
const [show, setShow] = useState(false);
useEffect(() => {
setShow(focus || Boolean(value));
}, [focus, value])
return (
<View
style={[
styles.container,
focus ? styles.containerFocus : {},
]}
>
<TouchableWithoutFeedback {...props}>
<View style={[
styles.wrapper,
focus ? styles.wrapperFocus : {},
]}>
<Text
style={[
styles.label,
show ? styles.labelActive : {},
]}
>
{label}
</Text>
{show && children}
</View>
</TouchableWithoutFeedback>
</View>
);
};
export default InputWrapper;
I implemented the wrapper with the TextInput native component:
import React, { ReactElement, useState, useRef } from 'react';
import {
TextInputProps, TextInput,
} from 'react-native';
import InputWrapper from './InputWrapper';
import InputValue, { InputValueStyles } from './InputValue';
interface Props extends TextInputProps {
label?: string;
}
const Input = ({
label, value, ...props
}: Props): ReactElement => {
const [focus, setFocus] = useState(false);
const input = useRef(null);
const onWrapperPress = (): void => {
setFocus(true);
}
const onInputFocus = (): void => {
setFocus(true);
}
const onInputBlur = (): void => {
setFocus(false);
}
return (
<InputWrapper
label={label}
value={value}
focus={focus}
onPress={onWrapperPress}
>
{!focus && <InputValue value={value} />}
{focus && <TextInput
autoFocus={focus}
style={InputValueStyles.input}
value={value}
onFocus={onInputFocus}
onBlur={onInputBlur}
ref={input}
{...props}
/>}
</InputWrapper>
);
};
export default Input;
All works perfectly except one thing: If you press on the touchable overlay of the focuses input, you will lose this focus because it triggered the blur input event.
The question is quite simple then: How to prevent this?
I also tried other ways with the disabled prop of the touchable area or blur event preventing without any luck.
If you also have a better way to handle my need, I'm not against propositions. :-)
Related
There is way to save switch button state with asyncstorage ?
My goal is that when the user clicks on the switcher, then its state will be preserved even when the user exits the application and returns back.
import { View, Text, Switch } from 'react-native';
import React from 'react';
import styles from './ViewFieldStyles';
type Props = {
title: string;
value: boolean;
setValue: () => void;
};
const ViewField = ({ title, value, setValue }: Props) => {
return (
<View style={styles.optionView}>
<View style={styles.sameRowTextView}>
<Text style={styles.optionText}>{title}</Text>
<View style={styles.switchView}>
<Switch
trackColor={{ false: '#767577', true: 'rgba(4, 76, 163, 0.38)' }}
thumbColor={value ? '#1d16db' : '#f4f3f4'}
ios_backgroundColor='#3e3e3e'
onValueChange={setValue}
value={value}
/>
</View>
</View>
</View>
);
};
export default ViewField;
You can easily do that using AsyncStorage.
Below is an example.
Install and import module.
import AsyncStorage from '#react-native-async-storage/async-storage';
Then use it like below.
<Switch
trackColor={{false: theme.negative, true: theme.positive}}
onValueChange={(result) => {
AsyncStorage.setItem(
AsyncStorageKey.DarkMode,
JSON.stringify(result),
);
changeThemeType(result ? 'dark' : 'light');
setIsDarkMode(result);
}}
value={isDarkMode}
/>
To get the value when component loads,
const [isDarkMode, setIsDarkMode] = useState(false);
useEffect(() => {
const setDarkMode = async (): Promise<void> => {
const result = JSON.parse(
(await AsyncStorage.getItem('IS_DARK')) || 'false',
) as boolean;
setIsDarkMode(result);
};
setDarkMode();
}, []);
I have a component that takes an object and passes it to a new screen upon navigating to the screen. However, when I go to the next screen the object passed is undefined. What am I doing wrong here? I have done the exact same thing with another component and it works perfectly fine, but on this component, it isn't passing the parameter properly. Is there something else I need to configure in the navigator?
GoalCard.JS
import * as React from 'react';
import 'react-native-get-random-values';
import { v4 as uuidv4 } from 'uuid';
import { useNavigation } from "#react-navigation/core";
import { Card, Chip, Divider, Paragraph, Text, Title } from 'react-native-paper';
const GoalCard = ({ item }) => {
const navigation = useNavigation();
const goals = JSON.parse(item.Goals);
const tasks = JSON.parse(item.GoalTasks);
const [goalsData, setGoalsData] = React.useState(
{
GoalName: item.GoalName,
GoalID: item.GoalID,
GoalDescription: item.GoalDescription,
GoalComplete: item.GoalComplete,
GoalTasks: tasks,
Goals: goals,
UserID: item.UserID,
createdAt: item.createdAt,
updatedAt: item.updatedAt
}
);
return(
<Card className="card">
<Card.Content>
<Title>Goal Set: {item.GoalName}</Title>
<Divider/>
<Paragraph>
<Chip
onPress={() => {
navigation.navigate(
'Goals', {
goalsData: goalsData
});
}}
>
Edit
</Chip>
<Text onPress={() => console.log(goalsData)}>Log</Text>
<Text>Description: {item.GoalDescription}</Text>
<Divider/>
{Object.entries(goals).map(obj => (
<Text key={uuidv4()}>{obj[1].goal}{" "}</Text>
))}
</Paragraph>
</Card.Content>
</Card>
);
}
export default GoalCard;
GoalScreen.js
Pressing "Log" as seen in this file returns undefined
import React from "react";
import { ScrollView, View } from "react-native";
import { Text } from "react-native-paper";
import { MainStyles } from "../../styles/Styles";
const GoalScreen = ({ route }) => {
const { goalData } = route.params;
return (
<ScrollView>
<View style={MainStyles.col}>
<Text onPress={() => console.log(goalData)}>Log</Text>
</View>
</ScrollView>
);
};
export default GoalScreen;
There is a typo ... You are setting
goalsData: goalsData
But you are trying to read as below
const { goalData } = route.params;
try
const { goalsData } = route.params;
So im new at react native and i built an custom text input that gives suggestions by following a tutorial.But now i cant use onChange in that custom text input.I tried to create a state in App.js and change it in AutoCompleteText.js file but didnt worked.How can i get the value inmy custom text input ?
my App.js file :
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import AutoCompleteText from './AutoCompleText'
import './AutoCompleteText.css';
export default function App() {
return (
<View style={styles.container}>
<View style={styles.AutoComp}>
<AutoCompleteText items={['Ali','Veli','Ahmet']} />
</View>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
AutoComp:{
width:600
}
});
my AutoCompleteText.js file
import React from 'react'
import './AutoCompleteText.css';
export default class AutoCompleteText extends React.Component{
constructor(props){
super(props);
this.state={
suggestions:[],
text:'',
};
}
onTextChanged = (e) =>{
const {items} = this.props;
const value = e.target.value;
let suggestions = [];
if (value.length > 0){
const regex = new RegExp(`^${value}`,'i');
suggestions = items.sort().filter(v => regex.test(v));
}
this.setState(() => ({ suggestions , text: value }));
}
suggestionSelected(value){
this.setState(() =>({
text: value,
suggestions:[],
}) )
}
renderSuggestions(){
const {suggestions} = this.state;
if (suggestions.length === 0){
return null;
}
return(
<ul>
{suggestions.map((item) => <li onClick={() => this.suggestionSelected(item)}>{item}</li>)}
</ul>
);
}
render(){
const { text } = this.state;
return(
<div className="AutoCompleteText">
<input value={text} onChange={this.onTextChanged} type ='text'/>
{this.renderSuggestions()}
</div>
)
}
}
hi Ülkü Tuncer Küçüktaş,
You are writing the wrong syntax. You are mixing the syntax of react native with react. In react native for textinput, you should use the TextInput Component(Built in component from docs).
The syntax of react native TextInput look like below
import React from 'react';
import { TextInput } from 'react-native';
const UselessTextInput = () => {
const [value, onChangeText] = React.useState('Useless Placeholder');
return (
<TextInput
style={{ height: 40, borderColor: 'gray', borderWidth: 1 }}
onChangeText={text => onChangeText(text)}
value={value}
/>
);
}
export default UselessTextInput;
Hello Everyone!
I want to show a native popup menu in Android using UIManager.showPopupMenu() method, but I got an Error Can't display the popup. Could not find view with tag XXX at the docs the first parameter of that method should be filled with a view tag number using the findNodeHandle() method.
It works fine if I am using a ref from <Pressable ref={handlePopupRef} /> but It doesn't work if I am using a ref from <View ref={handlePopupRef}></View>
I have no idea what's wrong with my code even when I am trying with ref callback it still doesn't work like <View ref={ref => handlePopupRef.current = ref}></View>
Code Samples
Icon.tsx
import React, {forwardRef, RefAttributes} from 'react';
import {View, StyleProp, TextStyle} from 'react-native';
import Icon from 'react-native-vector-icons/Feather';
interface IconHelperProps {
name?: string;
size?: number;
color?: string;
style?: StyleProp<TextStyle>;
}
const IconHelper: React.ForwardRefExoticComponent<
IconHelperProps & RefAttributes<null>
> = forwardRef((props, ref) => {
return (
<View ref={ref}>
<Icon
name={props.name || 'help-circle'}
size={props.size || 24}
color={props.color || '#242424'}
style={[props.style]}
/>
</View>
);
});
export default IconHelper;
Button.tsx
import React, {useRef, useState} from 'react';
import {
Pressable,
UIManager,
findNodeHandle,
StyleProp,
TextStyle,
ViewStyle,
} from 'react-native';
import Icon from './Icon';
import Text from './Text';
export interface ButtonHelperProps {
title?: string;
iconName?: string;
iconSize?: number;
iconColor?: string;
isDropdown?: boolean;
dropdownMenu?: string[];
style?: StyleProp<ViewStyle>;
textStyle?: StyleProp<TextStyle>;
onPress?: () => void;
onPressDropdownMenu?: (i: number | undefined) => void;
}
const Button: React.FC<ButtonHelperProps> = props => {
const {title, iconName, isDropdown} = props;
const [bgColor, setBgColor] = useState('transparent');
const handlePopupRef = useRef(null);
return (
<Pressable
onPressIn={() => {
setBgColor('#E3E3E7');
}}
onPress={() => {
if (isDropdown) {
UIManager.showPopupMenu(
findNodeHandle(handlePopupRef.current),
props.dropdownMenu || ['Sample Menu'],
error => {
console.log(error);
console.error('Error Show Popup');
},
(e, i) => {
props.onPressDropdownMenu?.(i);
},
);
} else {
props.onPress?.();
}
}}
onPressOut={() => {
setBgColor('transparent');
}}
style={[
{
backgroundColor: bgColor,
borderRadius: title ? 8 : 0,
flexDirection: 'row',
alignItems: 'center',
},
props.style,
]}
>
// It's work fine when I am using a Ref from Pressable component
{/* {isDropdown && <Pressable ref={handlePopupRef}></Pressable>} */}
// But it doesn't work when I am using a Ref from View component
{iconName && (
<Icon
name={iconName}
size={props.iconSize}
color={props.iconColor}
ref={ref => (handlePopupRef.current = ref)}
/>
)}
{title && (
<Text
size={14}
bold
style={[
{
paddingHorizontal: 24,
paddingVertical: 12,
},
props.textStyle,
]}
>
{title}
</Text>
)}
</Pressable>
);
};
export default Button;
According to this link in the React Native API Documents:
https://facebook.github.io/react-native/docs/0.59/textinput#isfocused
The TextInput component has a method called isFocused(). How would I go about accessing this method? Do I have to use a ref?
Also, I already know that I can achieve the same effect by using the onFocus prop and setting up a state manager and a function to change the state of the input based on the onFocus. However, I am just curious how I would go about using these component methods since there are others in other components as well.
I have tried using this
<TextInput onChangeText={this.handleText} style={(this.isFocused()) ? styles.input : styles.lame} placeholder="Names"/>
but it is looking like I might have to use a ref since it seems that it isn't defined even though the method should be a part of this component.
isFocused() should be called on ref to TextInput.
import React, {useEffect, useRef} from 'react';
import {TextInput, BackHandler} from 'react-native';
function SearchBar() {
const textInputReference = useRef(null);
useEffect(() => {
let backhandler = BackHandler.addEventListener(
'hardwareBackPress',
function() {
if (textInputReference.current.isFocused()) {
textInputReference.current.blur();
return true;
}
return false;
},
);
return () => {
backhandler.remove();
};
}, []);
return (
<TextInput ref={textInputReference} />
);
}
export default SearchBar;
You can use state for handle input focus, if you have multi-input that also need focus state, just create many state for it.
class MyComponent extends React.Component {
state = { isFocused: false }
handleInputFocus = () => this.setState({ isFocused: true })
handleInputBlur = () => this.setState({ isFocused: false })
render() {
const { isFocused } = this.state
return (
<View>
<TextInput
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
style={ isFocused ? styles.input : styles.lame}
/>
<Text>Hello World</Text>
</View>
)
}
}
This another easy way to use the onFocus prop in TextInput
import React, { useState } from 'react'
import { View, StyleSheet, TextInput } from 'react-native'
const TextInput = () => {
const [isHighlighted, setIsHighlighted] = useState(false)
return (
<View>
<TextInput
style={[styles.textInput, isHighlighted && styles.isHighlighted]}
onFocus={() => { setIsHighlighted(true)}
onBlur={() => {setIsHighlighted(false)} />
</View>
)
}
const styles = StyleSheet.create({
textInput: {
borderColor: 'grey',
borderWidth: StyleSheet.hairlineWidth,
borderRadius: 8,
height: 43,
},
isHighlighted: {
borderColor: 'green',
}
})
According to its documentation
Returns true if the input is currently focused; false otherwise.
How we can achieve that? the answer is useRef.
For example :
import React, { useRef } from 'react'
import { View, StyleSheet, TextInput, Button } from 'react-native'
const App = props => {
const inputRef = useRef(null);
const checkIsFocusedHandler = () => {
const result = inputRef.current.isFocused();
alert(result);
}
return (
<View style={styles.container}>
<TextInput ref={inputRef} style={styles.input} value="Abolfazl Roshanzamir" />
<TextInput style={styles.input} />
<Button title="Check isFocused" onPress={checkIsFocusedHandler} />
</View>
)
}
If we click on the first TextInput then click on the button, the result is => true,
if we click on the second TextInput then click on the button, the result is => false.
Use onFocus props of TextInput component
<TextInput onFocus={yourCallBack} />
yourCallBack function will be called when the TextInput is focused