React Native - setNativeProps() on parentElement.props.children = undefined - reactjs

I'm developing a school management app for myself.
All students in my class are listed in a Flatlist with their parents' phone numbers beside to enable me to send them text messages when a student is absent.
I have a FlatList with Listitems, each of which contains a Touchopacity component with Text child inside.
On successful sending an sms to a student's parent (smsParent method) I want to setNativeProps on both TouchOpacity and its Text child (manipulate their style props). I use ref=(()=>...) to have reference to Touchopacity and then this.props.children (only 1 child) to get to its Text child.
Then however I cannot use setNativeProps (=undefined).
However, when I use ref=(()=>...) on Text as well and then refer to it, setNativeProps works /like in case of its parent/.
Why can't I use setNativeProps() on a child when refering to it by parentEl.props.children? (only 1 child, checked in debugger, it's properly identified)
Please read comments in smsParent method
/*sorry for inserting a snippet - code insertion was crazily formatted/
/**code simplified/
class SingleClassPage extends Component {
buttons = new Array();
constructor(props) {
super(props);
this.state = { students: [] };
this.smsParent = this.smsParent.bind(this);
}
componentDidMount() {
//fetch students from api and setState..
this._getStudentsList();
}
_getStudentsList() {
// ...
}
//flatlist item
ListEl(props) {
return (
<View>
<TouchableOpacity ref={el => { let a = props.item.attId + 'att'; props.buttons[a] = el; }}
style={[styles.buttonDef, (props.item.phone_parent ? styles.buttonBlue : styles.buttonGray)]}
onPress={() => { props.smsSendHandler(props.item, 'attendance', a) }}>
<Text style={props.item.phone_parent ? styles.buttonTextLight : styles.buttonTextDark}>
{props.item.smsAttSent ? 'sms sent' : 'sms send'}
</Text>
</TouchableOpacity>
</View>
)
}
render() {
return (
<View style={{ flex: 1, }}>
<FlatList
data={this.state.students}
extraData={this.state}
keyExtractor={item => item.attId}
renderItem={({ item }) => <this.ListEl buttons={this.buttons} item={item} smsSendHandler={this.smsParent} />}
/>
<BusyIndicator />
</View>
);
}
smsParent(student, msgCategory, smsButton) {
//call myjava module and upon successful callback call below:
let parEl = this.buttons[smsButton];
//childEl is an object with props.children set to text 'sms sent/send' when I watch its value in debugger
//so it's correctly identified
let childEl = parEl.props.children;
// WORKS
parEl.setNativeProps({ style: { backgroundColor: 'green' } });
// OOPS
childEl.setNativeProps({ style: { color: 'black' } });
}
}
edit1
Posting a screenshot of the error (also as response to Dyo's suggestion below - the same error Dyo...)

I think you have to iterate throught children to pass them nativeProps (even if there's only one child) :
smsParent(student, msgCategory, smsButton) {
//call myjava module and upon successful callback call below:
let parEl = this.buttons[smsButton];
React.Children.forEach(parEl.props.children, child => { child.setNativeProps({ style: { color: 'black' } }) });
parEl.setNativeProps({ style: { backgroundColor: 'green' } });
}

Related

REACT NATIVE - this.setState doesn't work inside onChangeText

I'm trying to create a conditional onPress here using a gender selection. If the gender is undefined, I can't go on. Quite simple. But I'm a newbie and I'm not finding the trouble here.
That's the gender prop in state...
constructor(props) {
super(props);
this.state = {
gender: ''
};
That's the const genderSelect with these 3 gender values...
render() {
const genderSelected = [
{ value: 'Feminino', label: 'Feminino' },
{ value: 'Masculino', label: 'Masculino' },
{ value: 'Outro', label: 'Outro' }
];
...
}
That's my Dropdown class where I call the genderSelect and try to use the setState (but it doesn't work, the 'console.log' keeps returning 'undefined'). And finally, that's my conditional onPress.
return (
<>
<Dropdown
label='Selecione...'
data={genderSelect}
baseColor={"white"}
textColor={"white"}
itemColor={"white"}
pickerStyle={{
backgroundColor: "black"
}}
onChangeText={(value) => {
this.setState({ gender : value })
console.log(this.gender)
}}
/>
<TouchableOpacity
style={styles.buttonContainer}
onPress= {()=>{
if (this.gender != undefined ) {
console.log(this.gender)
this.props.navigation.navigate("Abas")
}
else {
console.log(this.gender)
Alert.alert('Ops!','Favor preencher sua idade e gênero antes!');
}
}}
>
<Text style={styles.buttonText}>CONFIRMAR</Text>
</TouchableOpacity>
Btw, I suppose the problem ocurrs inside the onChangeText / setState section. No matter how I use 'if / else' afterwards, it keeps returning 'undefined'.
It’s probably a simple issue whose cause I didn’t get, I know. And I've searched a lot about it, but none of the answers actually helped me. Hope someone can help me.
Replace this.gender with this.state.gender
That's the error.. gender is a state variable so you have to access it like that.
In your return part change this
<TouchableOpacity
style={styles.buttonContainer}
onPress={() => {
if (this.state.gender != undefined) {
console.log(this.state.gender);
this.props.navigation.navigate('Abas');
} else {
console.log(this.state.gender);
Alert.alert('Ops!', 'Favor preencher sua idade e gênero antes!');
}
}}>
<Text style={styles.buttonText}>CONFIRMAR</Text>
</TouchableOpacity>

How to create a blinking react View

Can someone help me out how I can create a blink-able react-native component?
So basically, this is what I have done
class Blinkable extends PureComponent {
state = {
blinkComponentVisibility: false
}
blink () {
this.setState({blinkComponentVisibility: ! blinkComponentVisibility})
console.log(this.state)
}
componentDidMount = () => {
setTimeout(() => {this.blink}, 3000)
}
render () {
if (i === currentProgress) {
if (this.state.blinkComponentVisibility) {
progressBarArray.push(
<View
style={{
width: widthOfIndividualBlog,
backgroundColor: colorOfProgressBar,
height: heightOfProgressBar
}}
key={i}
></View>)
}
}
return (
<View>
<View style={{display: 'flex', flexDirection: 'row'}}>{progressBarArray}</View>
</View>
)
}
}
With the above code, I was expecting my component to blink but nothing happens rather I see the following logs in console
RCTLog.js:47 Could not locate shadow view with tag #363, this is
probably caused by a temporary inconsistency between native views and
shadow views
Can someone please help me in figuring out what I could be doing wrong?
this.setState({blinkComponentVisibility: ! blinkComponentVisibility}) should be this.setState({blinkComponentVisibility: ! this.state.blinkComponentVisibility})
and in your set timout you need to call the function
setTimeout(() => this.blink(), 3000)

react-native pass data to parent navigation from second navigation

Here is a part of the code of second screen:
state = {
hasCameraPermission: null,
barcodeValue : ""
}
FunctionToOpenFirstActivity = () =>
{
this.props.navigation.navigate('First', { barcodeValue: this.state.barcodeValue });
}
//after barcode was scanned
handleBarCodeScanned = ({ type, data }) => {
this.state.barcodeValue = data;
alert(this.state.barcodeValue);
this.FunctionToOpenFirstActivity();
}
Here is a part of the code of parent screen in render ()
<View style={styles.firstrow}>
<View style={styles.inputWrap}>
<Text style={styles.label}>Barcode Value</Text>
<TextInput style={styles.input}>{this.props.barcodeValue}</TextInput>
</View>
</View>
this.props.barcodeValue is blank, anyone knows what is the problem?
as you can find inside the documentation https://reactnavigation.org/docs/en/params.html, you can read the params that passed from previous view using this.props.navigation.getParam(paramName, defaultValue), defaultValue is optional
in your case, try changing this.props.barcodeValue to this.props.navigation.getParam('barcodeValue')

Fetching document as JSON using React-Native

I have found some similar solutions but not one that does exactly what I want.
Here is what I wanna do: I have a few documents saved as JSON in a server, I want to fetch those documents using React-Native and display them on my phone.
However, think about a solution when I don't have to change my code every time I upload a new document to the server. React-native should be able to fetch everything from the server, even the new documents, without having to add new lines of code in the return{}. Those documents might differ from each other, some includes only text, some include text and input fields, some include pictures, text and input fields.
If something is unclear please let me know in the comment section.
Any suggestion would be highly appreciated!
Example of JSON how it would look like:
{
"results":[
{
"contract":{
"title":"Contract test",
"content":"You can always follow the progress of your application by logging on the the application portal. Please note that all communication from DTU will take place via this portal. When we have sent you a message on the ..."
},
"fillable_fields": {
"FIELD_NAME_1": "FIELD_VALUE_1",
"FIELD_NAME_2": "FIELD_VALUE_2",
"FIELD_NAME_N": "FIELD_VALUE_N"
},
"picture":{
"medium":"https://www.healthcaredenmark.dk/media/11272/bridgeit_logo.png"
}
}
]
}
My code in React-Native:
class HomeScreen extends React.Component {
constructor() {
super();
this.state = {};
this.getRemoteData();
}
static navigationOptions = {
title: 'List of documents',
};
getRemoteData = () => {
const url = "https://demo8106568.mockable.io/results";
fetch(url)
.then(res => res.json())
.then(res => {
this.setState({
data: res.results
});
})
.catch(error => {
console.log("get data error from:" + url + " error:" + error);
});
};
capFirstLetter = (string) => {
return string.charAt(0).toUpperCase() + string.slice(1);
}
renderNativeItem = (item) => {
const contract =
this.capFirstLetter(item.contract.title);
//this.capFirstLetter(item.name.content);
return <ListItem
roundAvatar
title={contract}
subtitle={item.content}
avatar={{ uri: item.picture.thumbnail }}
onPress={() => this.onPressItem(item)}
/>;
}
onPressItem = (item) => {
this.props.navigation.navigate('Detail', {item: item})
}
render() {
return (
<View>
<FlatList
data={this.state.data}
renderItem={({item}) => this.renderNativeItem(item)}
/>
{/* <Button
title="Go Detail"
onPress={() => this.props.navigation.navigate('Detail', {source: "homescreen"})}
/> */}
</View>
);
}
}
class DetailScreen extends React.Component {
static navigationOptions = {
title: 'Content of selected'
};
render() {
const source = this.props.navigation.state.params.source;
const item = this.props.navigation.state.params.item;
let contract = "";
let img = "";
let inp = "";
let content ="";
if (item != null) {
contract = item.contract.title;
img = item.picture.medium;
content = item.contract.content;
inp = item.fillable_fields.FIELD_NAME_1;
}
return (
<View style={styles.container}>
<Text style={styles.text}>{contract} </Text>
<Image
style={{width: 300, height: 128}}
source={{uri: img}}
/>
<Text style={styles.text} > {content} </Text>
<TextInput style={{textAlign: 'center', borderWidth:1, marginBottom: 7, height: 50}} source={{uri: inp}}/>
<Button title="Go back to the list" onPress={this._goHome}/>
</View>
);
}
_goHome = async () => {
this.props.navigation.navigate('Home');
};
}
I understand what you are trying to accomplish. But I really don't think you can make it work like you want. You can compare it to calling a normal API endpoint. You will most likely have a method like:
getContracts() {
fetch('CONTRACTS_ENDPOINT').then(res => doSomethingWithContracts(res))
}
You already know that this data returns contracts and you already know what data to expect there. Therefore you can easy access fields like contract.name, or contract.date.
And when you want to call some other endpoint you will do something similar
getSomethingElse() {
fetch('OTHER_ENPOINT').then(res => ...)
}
You will know the data that comes with the OTHER_ENPOINT, so you can directly access its fields.
So my suggestion is, think of each of you document as a separate API endpoint. Of course, if you change your document, you will also need to change client side implementation, so for example if you rename contract.title to contract.otherWordForTitle then you will obviously need to change that on the client as well.
From what I know, what you want, to have the client always know the document structure without updating it in order to know that a document has changed, is not possible. But of course, I might be wrong and there can be a workaround :-)

How to setState of a particular index in an array React Native

I have an array of objects that I am currently mapping over to generate as buttons. When clicked, I want the background color of the specific button the user clicks on to change color ( I want it to toggle on, like a switch, so I can eventually save to async storage). Right now when the user clicks, all buttons change color. I'm not quite sure how I should handle this.setState in the selectMetric function.
import React, {Component} from 'react';
import {View, Text, ScrollView} from 'react-native';
import {Button} from 'react-native-elements';
const RISK_DATA = [
{id: 1, text: 'cats', flag: false, buttonColor: null},
{id: 2, text: 'dogs', flag: false, buttonColor: null},
]
class IssueSelectionScreen extends Component {
state = {flag: false, buttonColor: null}
selectMetric = (index) => {
for (let i=0; i < RISK_DATA.length; i++) {
if (index === (RISK_DATA[i].id - 1)) {
console.log("RISK_DATA:", RISK_DATA[i]); // logs matching id
// ------------------------------------------------------
// Problem setting correct state here:
RISK_DATA[i].buttonColor = this.setState({flag: true, buttonColor: '#03A9F4'})
// this.setState({flag: true, buttonColor: '#03A9F4'})
// this.setState({update(this.state.buttonColor[i], {buttonColor: {$set: '#03A9F4'}}) })
// ----------------------------------------------------------
}
}
}
showMetric() {
return RISK_DATA.map((metric, index) => {
return (
<View key={metric.id}>
<Button
raised
color={'black'}
title={metric.text}
borderRadius={12}
onPress={() => this.selectMetric(index)}
backgroundColor={this.state.buttonColor}
>
{metric.text}
</Button>
<Text>{/* intentionally blank*/} </Text>
</View>
)
})
}
render() {
return(
<ScrollView style={styles.wrapper}>
<View style={styles.issues}>
{this.showMetric()}
</View>
</ScrollView>
);
}
}
const styles = {
issues: {
justifyContent: 'center',
flexDirection: 'row',
flexWrap: 'wrap',
alignItems: 'flex-start',
marginTop: 10,
justifyContent: 'space-between',
},
wrapper: {
backgroundColor: '#009688'
}
}
export default IssueSelectionScreen;
so the short answer to your question would look something like this:
class IssueSelectionScreen extends Component {
constructor(props) {
super(props);
this.state = {
data: cloneDeep(RISK_DATA),
};
}
selectMetric = (index) => {
const tempData = cloneDeep(this.state.data);
tempData[index].flag = !tempData[index].flag;
this.setState({ data: tempData });
}
showMetric() {
return this.state.data.map((metric, index) => {
// same
})
}
render() {
// same
}
}
It involves putting the whole array of buttons into state since the state of those buttons is what can change. You could also maintain the flags as an array in state and keep the button info as a separate constant
This solution uses cloneDeep (from lodash) to prevent the code from mutating the state of the objects but you could probably also do it with this.state.data.map and creating new objects (which works as long as your objects aren't deeply nested).
If you're using Redux, the list would probably come into the component as a prop, then selectMetric would be dispatching an action to update the flag in Redux.
For anyone else viewing this post, the answer above is very helpful. To add a few last remarks, if you're trying to get the buttons to light up I added a simple if else to selectMetric:
if (tempData[index].flag) {
tempData[index].buttonColor = '#03A9F4';
console.log('tempData true:', tempData);
} else {
tempData[index].buttonColor = null;
console.log('tempData false:', tempData);
}
and updated the backgroundColor property on Button in showMetric with:
backgroundColor={this.state.data[index].buttonColor}

Resources