How can I use forwardRef in functional component in react native? - react-native

I made the following screen where each label + input combo is inside a child component. When I click save (salvar) I want to inspect each component's ref to see if it has a "isInvalid" variable set to true and then paint the border red and show an error message. That means the field is required, etc.
I've tried creating a ref in the parent component like this:
export default function DiaryExam({ navigation }) {
let examNameRef = useRef();
return (
<Page type="static" title={'Avaliação'} subtitle={'Editar'}>
<FormInput ref={examNameRef} inputType="text" title={"Nome da avaliação"} value={name} setValue={setName} required />
//I'll add the other refs below later
<FormInput inputType="text" title={"Código"} value={code} setValue={setCode} maxLength={8} required/>
<FormInput inputType="number" title={"Nota máxima"} value={maxGrade} setValue={setMaxGrade} />
<FormInput inputType="number" title={"Peso"} value={weight} setValue={setWeight} required />
<FormInput inputType="date" title={"Data de Início"} value={startDate} setValue={setStartDate} />
<FormInput inputType="date" title={"Data de Fim"} value={endDate} setValue={setEndDate} />
<FormInput inputType="switch" title={"Ignorar na Fórmula"} value={ignoreFormula} setValue={setIgnoreFormula} />
<Button style={[styles.button, {}]} textStyle={styles.buttonText} title={'Salvar'} onPress={() => saveExam()} requestFeedback />
</Page>
);
In the child component I have something like this ( among other things) :
export default function FormInput ({ inputType, title, value, setValue, required, maxLength, ref }) {
return (
<View>
{(inputType === 'text' || inputType === 'number') && (
<View>
<Text ref={ref} style={[styles.fieldValue, { borderColor: requiredValidationError ? 'red' : 'grey' }]}>{value}</Text>
<Text style={[{ alignSelf: 'flex-end', backgroundColor: 'white', color: requiredValidationError ? 'red' : 'grey' }]}>{requiredValidationError? 'Campo obrigatório' : ''}</Text>
</View>
)}
</View>
)
I need to see if the 'value' variable is length === 0 if the 'required' prop is true and then set setRequiredValidationError(true) so it'll change the layout accordingly.
But all I get is the following error in the console:
[Tue Mar 09 2021 17:21:48.858] ERROR Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
I have googled react native forwardRef, but all the examples and documentations are for traditional React or they use class components and nothing I've tried works. How would I do that?

As per the warning, you need to use React.forwardRef. You can do it like this:
const FormInput = React.forwardRef(({
inputType,
title,
value,
setValue,
required,
maxLength
}, ref) => {
return (
{(inputType === 'text' || inputType === 'number') && (
<View>
<Text ref={ref} style={[styles.fieldValue, { borderColor: requiredValidationError ? 'red' : 'grey' }]}>{value}</Text>
<Text style={[{ alignSelf: 'flex-end', backgroundColor: 'white', color: requiredValidationError ? 'red' : 'grey' }]}>{requiredValidationError? 'Campo obrigatório' : ''}</Text>
</View>
)}
)
}
More info in the React doc, which uses functional component.

Related

How to re-render a specific component in react native using reference?

I have to clear or re-render the DatePicker input on-pressing the clear/close icon. But there has no clear option in DatePicker. So I have to re-render the specific component(DatePicker) not whole UI.
import DatePicker from 'react-native-date-ranges';
<View style={styles.row}>
<Item rounded style={styles.inputWrap}>
<Input placeholder='Customer Id' keyboardType="numeric" placeholderTextColor='rgba(0,0,0,0.4)' onChangeText={(text) => this.setState({customerCode: text})}/>
</Item>
<View style={{flex: .025}}></View>
<Item rounded style={styles.inputWrap}>
<Input placeholder='Invoice No' keyboardType="numeric" placeholderTextColor='rgba(0,0,0,0.4)' onChangeText={(text) => this.setState({invoiceNo: text})}/>
</Item>
<DatePicker
style={ {height: 36, borderRadius: 8, borderColor: "#cccccc", borderWidth: 1,} }
customStyles = { {
placeholderText:{ fontSize:14 }, // placeHolder style
headerStyle : { backgroundColor:'#007aff' }, // title container style
headerMarkTitle : { }, // title mark style
headerDateTitle: { }, // title Date style
contentInput: { fontSize:14 }, //content text container style
contentText: {fontSize:14}, //after selected text Style
} } // optional
ButtonStyle={{backgroundColor:'#007aff', borderWidth:1, borderRadius:8,marginHorizontal: 20, borderColor:'#fff'}}
ButtonTextStyle={{color: '#fff',alignSelf:'center',padding:10, fontSize: 16}}
centerAlign // optional text will align center or not
allowFontScaling = {false} // optional
markText={'Select Date'}
ButtonText='Select'
placeholder={'Ex: Apr 27, 2018 → Jul 10, 2018'}
mode={'range'}
onConfirm={(text) => {this.setState({invoiceDate: text}); console.log('invoiceDate: ', text)}}
ref = {(ref)=> this.picker = ref}
/>
<Icon style={{padding: 10, marginLeft: -30, marginTop: -2}} size={20} name={'md-close'} color={'red'} onPress={() => "Clear or rerender the DatePicker Input. There are no clear option in DatePicker. So i have to rerender"}/>
</View>
I am new in react-native. So, I need your help badly
The library you're using is missing some props to set the selected date(s).
Use https://github.com/wix/react-native-calendars instead

Invarient Violation: Text strings must be rendered within a <Text> component , Occurs when using conditional operator in React Native

After using conditional operator to display some components based on some conditions, I got this error. Following is the code.
{this.state.isOk==false ? (
<View>
<TextInput value={this.state.title } />
<Text style={LocalStyles.errorText}>{this.state.errorTitle}</Text>
<TextInput value={ this.state.company } />
<Text style={LocalStyles.errorText}>{this.state.errorCompany}</Text>
<View>
<CheckBox value={this.state.isCurrent} />
</View>
{this.state.isCurrent==false ? (
<Date
value={this.state.from }
placeholder={strings("user_profile.from")}
maxDate={moment().subtract(1, "years")}
onChange={time => {
this.setState({ from: time });
}}/>
<Text style={LocalStyles.errorText}>{this.state.errorDate}</Text>
) : null}
<TextInput label={this.state.location} />
<Text style={LocalStyles.errorText}>{this.state.errorLocation}</Text>
<TextInput multiline={true} value={ this.state.description} />
<Text style={LocalStyles.errorText}>{this.state.errorDesc}</Text>
</View>
): null}
this is the style for Text component
errorText: {
color: "red",
paddingLeft: 10,
paddingRight: 10,
flexDirection: 'row',
},
Instead of returning null, which is handled like a text in this context, you should return an empty <View/>.

How to call a method of a component

I am using native-base datepicker and want to call the ShowDatePicker method from outside the component. It owuld be something like this except:
This.DatePicker doesnt exist
I dont know if that method is exposed, or how to reach it..
i think it has something to do with using refs?
Thank you!
<Content>
<Button onPress={this.DatePicker.showDatePicker}>
<DatePicker props}/>
</Content>
DatePicker source code: https://github.com/GeekyAnts/NativeBase/blob/master/src/basic/DatePicker.js
Well, if you have to ref it just like the another answer says, as follows
<Content>
<Button onPress={this.DatePicker.showDatePicker}>
<DatePicker ref={ref => this.DatePicker = ref } {...this.props}/>
</Content>
However this will not fix your issue unless DatePicker component takes a props as ref. In short, even if you do that in your component, you will not have access to the showDatePicker.
Rather trying to do so, you can do this in two way (assuming you are trying to showhide component on button click.
Option 1:
Use a prop showDatePicker which will show hide the component.
For ex,
<Content>
<Button onPress={this.setState({showHide: !this.state.showHide})}>
<DatePicker showDatePicker={this.state.showHide} {...this.props} />
</Content>
then in DatePicker use this prop to do some logic.
Or Option 2,
Use conditional operator to show hide the whole component. w
For ex,
<Content>
<Button onPress={this.setState({showHide: !this.state.showHide})}>
{this.state.showHide && <DatePicker {...this.props} />}
</Content>
Let me know if you wanted to do something else, I will update the answer.
EDIT:
Looking at your code in gist.github.com/fotoflo/13b9dcf2a078ff49abaf7dccd040e179, I figured what you are trying to do.
In short, you trying to show datepicker on click of a button. Unfortunately, this is not possible at the moment looking at Nativebase - how to show datepicker when clicking input? and the documentation https://docs.nativebase.io/Components.html#date-picker-def-headref.
If you really wanna have it, you should think about these possible solution,
Option 1: fork native-base do your manipulation and use the datepicker or even submit the PR to native-base for future use.
Option2: you can use any 3rd party library for eg: https://www.npmjs.com/package/react-native-modal-datetime-picker.
Or my favourite option 3:
import { TouchableOpacity, Text, Modal, View, Platform, DatePickerIOS, DatePickerAndroid } from 'react-native';
state = {
currentDate: date,
showiOSDatePicker: false,
chosenDate: date,
formattedDate
}
showDatePicker = async () => {
if (Platform.OS === 'ios') {
this.setState({
showiOSDatePicker: true
});
} else {
const { chosenDate, currentDate } = this.state;
try {
const {action, year, month, day} = await DatePickerAndroid.open({
date: chosenDate,
maxDate: currentDate
});
if (action !== DatePickerAndroid.dismissedAction) {
const dateSelected = new Date(year, month, day);
const formattedDate = this.getFormattedDate(dateSelected)
this.setState({chosenDate: dateSelected, formattedDate});
console.log(formattedDate)
}
} catch ({code, message}) {
console.warn('Cannot open date picker', message);
}
}
}
render() {
const { showiOSDatePicker } = this.state;
return (
<View>
{showiOSDatePicker &&
<Modal
animationType="fade"
transparent
visible={showiOSDatePicker}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}>
<View
style={{
display: 'flex',
flex: 1,
justifyContent: 'center'
}}
>
<View style={{
margin: 22,
backgroundColor: 'rgba(240,240,240,1)'
}}>
<View
style={{
borderBottomColor: 'rgba(87,191,229,1)',
borderBottomWidth: 2,
display: 'flex',
justifyContent: 'center',
height: 70,
paddingRight: 20
}}
>
<Text style={{
color: 'rgba(40,176,226,1)',
fontSize: 20,
paddingLeft: 20
}}>
{formattedDate}
</Text>
</View>
<DatePickerIOS
date={chosenDate}
onDateChange={this.setDate}
maximumDate={currentDate}
mode="date"
/>
<TouchableOpacity
style={{
borderTopColor: 'rgba(220,220,220,1)',
borderTopWidth: 1,
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: 50
}}
onPress={this.onCloseDatePicker}
>
<Text>
Done
</Text>
</TouchableOpacity>
</View>
</View>
</Modal>
}
<TouchableOpacity onPress={this.showDatePicker}>
<Text>Show Date</Text>
</TouchableOpacity>
</View>
);
}
Let me know if this make sense, or I will put together a working example in https://snack.expo.io/
Cheers
you have to give a ref to DatePicker
<Content>
<Button onPress={this.DatePicker.showDatePicker}>
<DatePicker ref={ref => this.DatePicker = ref } props}/>
</Content>
I had the same problem and this was my solution:
NativeBase DatePicker:
<DatePicker
defaultDate={new Date(2018, 4, 4)}
minimumDate={new Date(2018, 1, 1)}
maximumDate={new Date(2020, 12, 31)}
locale={"es"}
timeZoneOffsetInMinutes={undefined}
modalTransparent={true}
animationType={"fade"}
androidMode={"default"}
placeHolderText="Select date"
textStyle={{ color: "green" }}
placeHolderTextStyle={{ color: "#d3d3d3" }}
onDateChange={this.setDate.bind(this)}
disabled={false}
ref={c => this._datePicker = (c) }
/>
And with this you can open the datePicker:
<Button onPress={()=>{ this._datePicker.setState({modalVisible:true})}}>
<Text>
showDatePicker
</Text>
</Button>
I hope it helps

React Native - Change other icons color on click

I've been searching for a few days to solve this problem. I need to change another icon's color when i click in one of them.
I'm using react-native-vector-icons
this.setState({
listaPlantel: Object.entries(dataArray).map(function ([key, nome]) {
if (that.state.mercado.status_mercado == 2) {
dadosAtleta = that.state.pontuados[nome.atleta_id];
}
return (
<ListItem avatar key={key} button onPress={() => that.detailsScreen(nome)}>
<Left>
<Thumbnail source={{ uri: nome.foto.replace('FORMATO', '80x80') }} />
</Left>
<Body>
<Text>{nome.apelido}</Text>
<Text note>{that.state.posicoes ? that.state.posicoes[nome.posicao_id]['nome'] : ''} - {that.state.clubes ? that.state.clubes[nome.clube_id]['nome'] : ''}</Text>
<Text style={{ textAlign: 'left' }}>Última: {nome.pontos_num} Média: {nome.media_num} {' $' + nome.preco_num}</Text>
</Body>
<Right>
{/*<Text>{dadosAtleta ? dadosAtleta['pontuacao'] : nome.pontos_num}</Text>*/}
<Icon name="md-close-circle" size={30} />
<Icon type="Foundation" name="md-contact" key={key} size={30} color={that.state.id_capitao === nome.atleta_id ? that.state.corCap : that.state.corGeral} onPress={() => that.setState({ id_capitao: nome.atleta_id })} />
</Right>
</ListItem>
)
}),
});
It seems you are putting conditions and functions within setState I would recommend you read about the lifecycle and app state here:
https://reactjs.org/docs/state-and-lifecycle.html
As an example of how to update values, of which you're trying to do - take this scenario into consideration:
initial colour : red, updated colour : blue (For example)
in your constructor:
constructor (props) {
super(props);
this.state = {
/*Initial State and Colour*/
iconColour : "red"
}
}
in your render method:
<ListItem avatar key={key} button onPress={() => that.detailsScreen(nome)}>
<Icon color={this.state.iconColour}/>
</ListItem>
Within your onPress function:
this.setState({
iconColor : "blue"
})

Conditional Rendering on Items of Native Base Picker [React Native]

I’m using ‘Native Base’ components for our product and going good with this,
but I’m stuck at one point and it is around putting Items in Nativebase Picker. My code is like this
Render Method code -
render(){
return (
<View style={{marginTop: 20, flexDirection:'row', flexWrap:'wrap', justifyContent:'space-around', alignItems:'center'}}>
<View style={{flex:1, justifyContent:'center', alignItems:'flex-end' }}>
<Button
style={{ backgroundColor: '#6FAF98', }}
onPress={this._showDateTimePicker}
>
<Text>Select Date</Text>
</Button>
</View>
<View style={{flex:1, justifyContent:'center', alignItems:'stretch'}}>
<Picker
style={{borderWidth: 1, borderColor: '#2ac', alignSelf:'stretch'}}
supportedOrientations={['portrait','landscape']}
iosHeader="Select one"
mode="dropdown"
selectedValue={this.state.leaveType}
onValueChange={(value)=>this.setState({leaveType:value,})
//this.onValueChange.bind(this)
}>
<Item label="Full Day" value="leave1" />
{
this.showStartDateFirstHalf() // Here I want to show this picker item on the basis of a condition
}
<Item label="2nd half" value="leave3" />
</Picker>
</View>
<DateTimePicker
isVisible={this.state.isStartDatePickerPickerVisible}
onConfirm={this._handleDatePicked}
onCancel={this._hideDateTimePicker}
mode='date'
/>
</View>
);
}
showStartDateFirstHalf()
{
if(!this.state.isMultipleDays)
{
return(
<Item label="1st Half" value="leave2" />
);
}
}
So, this code is working fine if this.state.isMultipleDays is false, But when this.state.isMultipleDays is true, it means when it is in else part then i'm getting this error -
Cannot read property 'props' of undefined
I think there's an easier answer to this. Instead of creating the separate showStartDateFirstHalf() function try this:
render() {
const pickerItems = [
{
label: 'Full Day',
value: 'leave1',
},
{
label: '1st Half',
value: 'leave2',
},
{
label: '2nd Half',
value: 'leave3',
},
];
const filteredItems = pickerItems.filter(item => {
if (item.value === 'leave2' && this.state.isMultipleDays) {
return false;
}
return true;
});
// The 'return' statement of your render function
return (
...
<Picker ...>
{(() =>
filteredItems.map(item =>
<Item label={item.label} value={item.value} />
)()}
</Picker>
...
);
}
That way, you already have a list of items that is determined before the return statement of the render cycle. Also the use of filter instead of map will not just give you null as the second item if the condition is not met, but will remove the item altogether.