React-native FlatList rendering issue - reactjs

THE PROBLEM
see the screenshot of probleme
//below code is which loades the slider..you can also find the below snippet in ClassFlatListRender.js , when id of element passed through FlatList which equals to the selected Id then the audio slider should work, but if I trigger one audio file element all of the sliders are moving
{id == this.state.selectedOptionId ?
(<View style={[styles.viewBar, { flexDirection: 'row', }]}>
<View style={[styles.viewBarPlay, { width: playWidthMessage }]} />
</View)
:
<View><Text>no slider<Text></View>}
THE CODE
The structure of my code is this: I have a container component with all the logic and state,
which contains a FlatList component , which again contains a custom presentational List.
Container
Custom list component that includes the FlatList component and the renderItem method
invokes classFlatList component (presentational, stateless)
The container includes this component
<CustomList
items={this.state.data}
/>
CustomList:
export class CustomList extends Component {
render() {
const renderItem = ({ item }) => {
return (
<ClassFlatListRender
message={item.message}
id={item.id}
user={item.user}
timestamp={item.timestamp}
heights={item.heights}
url={item.url}
audioLength={item.audioLength}
type={item.type}
/>
)
}
let { items } = this.props
return (
<View style={styles.container}>
< FlatList
style={styles.container}
data={items.slice().sort((a, b) => b.timestamp - a.timestamp)}
// data={data}
renderItem={renderItem}
inverted={true}
extraData={items}
keyExtractor={(items) => `${items.timestamp}`}
/>
</View>
)
}
}
The ClassFlatListRender.js
export class ClassFlatListRender extends React.Component {
constructor() {
super();
this.state = {
update: false,
currentPositionSec: 0,
currentDurationSec: 0,
playTime: '00:00:00',
duration: '00:00:00',
id: '',
tempId: '',
selectedOptionId: '',
setId: '',
};
this.audioRecorderPlayer = new AudioRecorderPlayer();
this.audioRecorderPlayer.setSubscriptionDuration(0.09); // optional. Default is 0.1
}
onStartPlay = async (id, url) => {
console.log('onStartPlay');
//? Custom path
// const msg = await this.audioRecorderPlayer.startPlayer(this.path);
//? Default path
console.log("arvinf", url)
const msg = await this.audioRecorderPlayer.startPlayer(url);
console.log("msg", msg)
const volume = await this.audioRecorderPlayer.setVolume(1.0);
console.log(`file: ${msg}`, `volume: ${volume}`);
this.audioRecorderPlayer.addPlayBackListener((e) => {
this.setState({
currentPositionSec: e.currentPosition,
currentDurationSec: e.duration,
// playWidth: (e.currentPosition / e.duration) * (screenWidth - 300)
// playTime: this.audioRecorderPlayer.mmssss(
// Math.floor(e.currentPosition),
// ),
// duration: this.audioRecorderPlayer.mmssss(Math.floor(e.duration)),
});
});
};
onPausePlay = async () => {
await this.audioRecorderPlayer.pausePlayer();
};
renderElement = (id, audioLength) => {
if (this.state.selectedOptionId == id) {
// this.onStartPlay(url)
return (
<Fcon name={this.state.selectedOptionId == id ? 'play' : 'pasue'} size={24} color={'white'} />
)
}
if (this.state.selectedOptionId !== id || this.state.currentPositionSec == this.state.duration) {
// this.onPausePlay(url)
return (
<Fcon name={'play'} size={24} color={'white'} />
)
}
}
checkFunction = async (id, url) => {
console.log("id: before", id)
await this.setState({
selectedOptionId: id,
setId: id
})
console.log('id:after', this.state.selectedOptionId)
id == this.state.setId ? this.onStartPlay(id, url) : this.onPausePlay()
// return this.state.selectedOptionId
}
render() {
let { id, user, url, heights, message, audioLength, type } = this.props;
let playWidthMessage =
(this.state.currentPositionSec / this.state.currentDurationSec) *
(screenWidth - 300);
if (!playWidthMessage) {
playWidthMessage = 0;
}
return (
<View id={id}>
{
url.endsWith(".jpg") ?
< View key={id} style={[user == 1 ? styles.receiver : styles.sender, { paddingHorizontal: 14, paddingVertical: 8, borderRadius: heights > 50 ? 20 : 50 }]}>
<View style={{ alignItems: 'center', }}>
{console.log("address", url)}
<Image
resizeMode="contain"
// resizeMethod="scale"
style={{ width: 200, height: 200 }}
source={{ uri: url, width: 200, height: 200 }}
// source={{ uri: "https://picsum.photos/200", width: 200, height: 200 }}
// source={require("https://ibb.co/hM8BbY5")}
/>
</View>
</View> : null
}
{
url.endsWith(".mp3") ?
(
<View key={this.props.id} style={[user == 1 ? styles.receiver : styles.sender, { paddingHorizontal: 14, paddingVertical: 8, borderRadius: heights > 50 ? 20 : 50 }]}>
<View style={[styles.viewPlayer, { backgroundColor: user == 1 ? darkgrey : '#0096FF', alignSelf: 'center', borderRadius: 50, flexDirection: 'row', justifyContent: "center", alignItems: 'center' }]}>
{/* line */}
<TouchableOpacity activeOpacity={0.5} onPress={() => {
this.checkFunction(id, url)
}} style={{ borderRadius: 50, width: 36, height: 36, justifyContent: 'center', alignItems: 'center' }}>
<View>
{this.renderElement(id, url, audioLength)}
</View>
</TouchableOpacity>
<View >
<TouchableOpacity
style={[styles.viewBarWrapper, { width: screenWidth - 300 }]}
onPress={this.onStatusPress}
>
{id == this.state.setId ?
(<View style={[styles.viewBar, { flexDirection: 'row', }]}>
{/* {console.log("playWidth:", this.state.playWidth)} */}
<View style={[styles.viewBarPlay, { width: playWidthMessage }]} />
</View>
)
: <View><Text>ddd</Text></View>}
</TouchableOpacity>
</View>
<Text style={[styles.txtRecordCounter, { color: 'white' }]}>{audioLength}</Text>
</View>
</View >) : null
}
{
type == "txt" ?
// && user == 1 ?
<View key={id} >
<TouchableOpacity onPress={async () => await this.setState({ tempId: id })} style={[styles.receiver, { borderRadius: heights > 50 ? 20 : 50, backgroundColor: this.state.tempId == id ? 'pink' : 'yellow' }]}>
<Text style={styles.receiverText}>{message}</Text>
</TouchableOpacity>
</View> : null
}
</View >
)
}
}

Maintain the selected audio id in your parent component, pass it to child components using props, animate based on the condition. in ur message component

Related

React Native Update Parent Array from Child Component

I am having trouble updating an array that is passed as a prop into my child component. I have searched around but haven't found an answer that can directly solve my problem. My code is as follows:
App.js
import React, { Component } from 'react';
import { StyleSheet, Text, View, SafeAreaView } from 'react-native';
import { NavigationContainer } from '#react-navigation/native';
import { createNativeStackNavigator } from '#react-navigation/native-stack';
import AddMedication from "./src/AddMedication";
import MedicationList from './src/MedicationList';
const Stack = createNativeStackNavigator();
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
medications: [],
}
this.addMedication = this.addMedication.bind(this);
}
addMedication = (name, dosage, measurement, timesDaily) => {
console.log("Medication added.")
var newItem = {name: name, dosage: dosage, measurement: measurement, timesDaily: timesDaily}
this.setState({
medications: [...this.state.medications, newItem]
})
}
render() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Medication List">
{(props) => <MedicationList {...props} medications={this.state.medications} />}
</Stack.Screen>
<Stack.Screen name="Add New Medication">
{(props) => <AddMedication {...props} addMedication={this.addMedication} />}
</Stack.Screen>
</Stack.Navigator>
</NavigationContainer>
);
}
}
This is the home screen where I am trying to display the array but nothing shows up
MedicationList.js
class MedicationList extends Component {
constructor(props) {
super(props);
this.state = {
tableHead: ['Name', 'Dosage', 'Times Daily', 'Prescriber', 'For Diagnosis'],
}
}
medication = ({ item }) => {
<View style={{ flexDirection: 'row' }}>
<View style={{ width: 50, backgroundColor: 'lightyellow'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold', textAlign: 'center'}}>{item.name}</Text>
</View>
<View style={{ width: 400, backgroundColor: 'lightpink'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold' , textAlign: 'center'}}>{item.dosage}{item.selectedMeasurement}</Text>
</View>
<View style={{ width: 400, backgroundColor: 'lavender'}}>
<Text style={{ fontSize: 16, fontWeight: 'bold' , textAlign: 'center'}}>{item.timesDaiy}</Text>
</View>
</View>
}
render() {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', marginTop: '10%'}}>
<Button
title="+ Add New Medication"
onPress={() => {
/* 1. Navigate to the Details route with params */
this.props.navigation.navigate('Add New Medication', {
medications: this.props.medications,
});
}}
/>
<FlatList
data={this.props.medications}
renderItem={this.medication}
/>
</View>
);
}
}
This is where I click the add button to update the medications array
AddMedication.js
class AddMedication extends Component {
constructor(props) {
super(props);
this.state = {
name: '',
dosage: 0,
selectedMeasurement: "mg",
timesDaily: '',
prescriber: '',
forDiagnoses: '',
instructions: '',
validity: false,
};
}
setName = (name) => {
let isValid = this.isFormValid();
this.setState({ name: name, validity: isValid });
}
setDosage = (dosage) => {
let isValid = this.isFormValid();
this.setState({ dosage: dosage, validity: isValid });
}
setMeasurement = (measurement) => {
this.setState({ selectedMeasurement: measurement });
}
setTimesDaily = (timesDaily) => {
let isValid = this.isFormValid();
this.setState({ timesDaily: timesDaily, validity: isValid });
}
setPrescriber = (prescriber) => {
this.setState({ prescriber: prescriber });
}
setDiagnoses = (diagnoses) => {
this.setState({ forDiagnoses: diagnoses });
}
setInstructions = (instructions) => {
this.setState({ instructions: instructions });
}
isFormValid = () => {
return (this.state.name !== '' && (this.state.dosage !== '' && this.state.dosage > 0)
&& (this.state.timesDaily !== '' && this.state.timesDaily > 0));
}
render() {
return (
<View style={styles.container}>
<Text style={{color: 'red', marginBottom: 5, marginLeft: -125}}>* denotes required field</Text>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(name) => this.setName(name)}
placeholder="Medication Name"
value={this.state.name}
/>
</View>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(dosage) => this.setDosage(dosage)}
placeholder="Dosage"
value={this.state.dosage}
/>
</View>
<View style={styles.dosageContainer}>
<Text style={{flex: 1, marginTop: 100, marginLeft: 30}}>
Select Measurement:
</Text>
<Picker
style={styles.picker}
selectedValue={this.state.selectedMeasurement}
onValueChange={(itemValue, itemIndex) =>
this.setMeasurement(itemValue)
}>
<Picker.Item label="mg" value="mg" />
<Picker.Item label="g" value="g" />
<Picker.Item label="ml" value="ml" />
</Picker>
</View>
<View style={{flexDirection: 'row'}}>
<Text style={styles.required}>*</Text>
<TextInput
style={styles.inputText}
onChangeText={(timesDaily) => this.setTimesDaily(timesDaily)}
placeholder="Times daily"
value={this.state.timesDaily}
/>
</View>
<TextInput
style={styles.inputText}
onChangeText={(prescriber) => this.setPrescriber(prescriber)}
placeholder="Prescriber"
value={this.state.prescriber}
/>
<TextInput
style={styles.inputText}
onChangeText={(diagnoses) => this.setDiagnoses(diagnoses)}
placeholder="For diagnoses"
value={this.state.forDiagnoses}
/>
<TextInput
style={styles.inputText}
onChangeText={(instructions) => this.setInstructions(instructions)}
placeholder="Instructions"
value={this.state.instructions}
/>
<TouchableOpacity
style={this.isFormValid() ? styles.validButton : styles.invalidButton}
disabled={!Boolean(this.state.name && this.state.dosage && this.state.timesDaily)}
onPress={() => {
this.props.navigation.goBack()
this.props.addMedication(this.state.name, this.state.dosage,
this.state.selectedMeasurement, this.state.timesDaily)
}}
>
<Text style={{color: 'white'}}>Add Medication</Text>
</TouchableOpacity>
</View>
)
}
}
You can pass the state value but I think you cannot pass the addMedication method just like this.
Could you please try passing an arrow function that uses the setState method?
For example:
<Stack.Screen name="Add New Medication">
{(props) => <AddMedication {...props} addMedication={(name, dosage, measurement, timesDaily)=> {this.addMedication(name, dosage, measurement, timesDaily)}} />}
</Stack.Screen>

API image not being displayed in list

I know there are several questions with this issue but mine is different.
I trying to display an image in my Flatlist card that is coming from an API. However it is not showing up.
BUT...when I display this image in another part of my code (in an Autocomplete list) using the same code basically, it works. Also, when I try an url from an image on the Web, it displays inside the flatlist correctly
Here's my Flatlist code:
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
<Card>
<CardItem>
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
</CardItem>
<CardItem>
<View>
<Text style={styles.usergameText}>
{item}
</Text>
</View>
</CardItem>
</Card>
)}
keyExtractor={(item,index) => index.toString()}
/>
Here is my Autocomplete code in which I use the same image bracket-thingy
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.autocompleteList} >
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.gameText}>
{item.name}
</Text>
</TouchableOpacity>
</View>
I ran console.item(item.background_image) both inside the Flatlist(first snippet) and the Autocomplete list (Second snippet). The first shows 'undefined' and the second it shows all the URIs
App.js full code:
/*This is an example of AutoComplete Input/ AutoSuggestion Input*/
import React, { Component } from 'react';
//import react in our code.
import { StyleSheet, Text, TouchableOpacity, View, Image, FlatList, Alert, TouchableWithoutFeedback, Keyboard } from 'react-native';
//import all the components we are going to use.
import Autocomplete from 'react-native-autocomplete-input';
import { Button, List, Container, ListItem, Card, CardItem, Header, Item } from 'native-base';
import { Entypo } from '#expo/vector-icons'
//import Autocomplete component
//const API = 'https://api.rawg.io/api/games?page=1';
//Demo base API to get the data for the Autocomplete suggestion
class App extends Component {
constructor(props) {
super(props);
//Initialization of state
//films will contain the array of suggestion
//query will have the input from the autocomplete input
this.state = {
myGamesArray: [],
games: [],
query: ' ',
};
}
componentDidMount() {
//First method to be called after components mount
//fetch the data from the server for the suggestion
fetch('https://api.rawg.io/api/games?page=1&platforms=18', {
"method": "GET",
"headers": {
"x-rapidapi-host": "rawg-video-games-database.p.rapidapi.com",
"x-rapidapi-key": "495a18eab9msh50938d62f12fc40p1a3b83jsnac8ffeb4469f"
}
})
.then(res => res.json())
.then(json => {
const { results: games } = json;
this.setState({ games });
//setting the data in the games state
});
}
findGame(query) {
let i;
//method called everytime when we change the value of the input
if (query === '') {
//if the query is null then return blank
return [];
}
const { games } = this.state;
//making a case insensitive regular expression to get similar value from the film json
const regex = new RegExp(`${query.trim()}`, 'i');
//return the filtered game array according the query from the input
return games.filter(game => game.name.search(regex) >= 0);
}
AddItemsToArray = () => {
var i
//verifica se input esta vazio
if (this.state.query === '') {
return Alert.alert('Voce não selecionou um jogo')
}
//VERIFY IF GAME IS IN THE ARRAY
for (i = 0; i < this.state.games.length - 1; i++) {
if (this.state.query !== this.state.games[i].name) {
if (i === this.state.games.length - 2) {
return Alert.alert('Este jogo nao existe')
}
else {
continue
}
} else {
break
}
}
//verifica repetido
if (this.state.myGamesArray.includes(this.state.query)) {
return Alert.alert('Este jogo já foi adicionado')
}
else {
//Adding Items To Array.
this.setState(prevState => {
const { myGamesArray, query } = prevState;
return {
myGamesArray: [...myGamesArray, query.toString()],
};
},
// Use setState callback to alert with the updated state
);
}
}
render() {
const { query } = this.state;
const games = this.findGame(query);
const comp = (a, b) => a.toLowerCase().trim() === b.toLowerCase().trim();
return (
<TouchableWithoutFeedback
onPress={() => {
Keyboard.dismiss()
}}
>
<View style={styles.container}>
<View style={styles.listContainer}
>
<FlatList
data={this.state.myGamesArray}
renderItem={({ item }) => (
console.log(item.background_image),
<Card style={{flexDirection:'row',paddingEnd:100}}>
<CardItem cardBody>
<View>
<Image
style={styles.listImage}
source={{uri: item.background_image}}
/>
</View>
</CardItem>
<CardItem>
<View>
<Text style={styles.usergameText}>
{item}
</Text>
<Text style={styles.usergameText}>
Playstation 4
</Text>
</View>
</CardItem>
</Card>
)}
keyExtractor={(item,index) => index.toString()}
/>
</View>
<View>
<Header span
style={styles.header}>
<Autocomplete
inputContainerStyle={{borderColor:'transparent'}}
style={styles.autocompleteInput}
autoCapitalize="none"
autoCorrect={false}
//data to show in suggestion
data={games.length === 1 && comp(query, games[0].name) ? [] : games}
//default value if you want to set something in input
defaultValue={query}
/*onchange of the text changing the state of the query which will trigger
the findGame method to show the suggestions*/
onChangeText={text => this.setState({ query: text })}
placeholder=" Adicione os jogos que você tem"
//This below is the 'list' of autocomplete options
renderItem={({ item }) => (
//you can change the view you want to show in suggestion from here
//I GET ERROR WHEN TRYING TO ERASE (PS4) IN TEXT BOX ***NEED TO CHECK THIS
<View style={styles.iconContainer} >
<TouchableOpacity onPress={() => this.setState({ query: item.name})}
style={styles.autocompleteList} >
<View>
<Image
style={styles.gameImage}
source={{uri: item.background_image}}
/>
</View>
<Text style={styles.gameText}>
`${item.name}`
</Text>
</TouchableOpacity>
</View>
)}
/>
</Header>
</View>
<TouchableOpacity
style={styles.addButton}
onPress={() => this.AddItemsToArray()}
>
<Entypo name="plus" size={50} color="#fff" />
</TouchableOpacity>
</View>
</TouchableWithoutFeedback>
);
}
}
const styles = StyleSheet.create({
container: {
backgroundColor: '#fff',
flex: 1,
},
autocompleteInput: {
borderWidth: 1,
backgroundColor: "#fff",
borderColor: '#7843FF',
height: 50,
marginTop: 70,
borderRadius:10,
},
autocompleteList: {
flex:1,
flexDirection: 'row',
borderWidth:0.5,
borderColor: '#7843FF',
paddingVertical: 5,
paddingRight: 60,
},
listContainer: {
flex: 1,
position: 'absolute',
left: 10,
right: 10,
top:150,
flexDirection:'column',
justifyContent: 'center',
borderColor: '#7843FF',
},
gameText: {
fontSize: 15,
marginLeft: 10,
marginRight:30,
marginVertical:10,
color: '#000',
textAlign: 'left',
justifyContent: 'center'
},
usergameText: {
fontSize:15,
textAlign: 'left',
alignSelf:'stretch',
color: '#000',
},
gameImage: {
flex: 3,
height: 60,
width: 60,
marginLeft:10,
borderRadius: 100,
},
listImage: {
flex: 3,
height: 110,
width: 90,
marginLeft:0,
},
addButton: {
height:50,
width: 50,
position: 'absolute',
left: 371,
top: 71,
backgroundColor: '#7843FF',
borderTopRightRadius: 10,
borderBottomRightRadius:10,
},
usergameImage: {
height: 100,
width: 100,
borderRadius: 100,
},
header: {
backgroundColor:'#67E6DC'
}
});
export default App;

React-navigation not always pass parameters to screens

I'm migrating my old project (from RN 0.57) to latest one (0.62.2), but I'm having some troubles with react-navigation v5.
I have one screen called "Race" where I have racers and their times. When user click in one specific racer, a new screen (called 'PersonalRank') are open with data from this particular racer.
This second screen takes tx_id as parameter to get data from redux/state. The problem is that not always this data is being passed to screen. I have made a little video to show the problem in simulator (the same happens on real smartphone). As you can see in log screen, the tx_i is correct while calling the new screen.....but is not always changing in the props of new screen.
This is the URL of demo video: https://youtu.be/pjhH6pOMRhs (1:55 minute video).
In the races list, I'm using this code to call the second screen (note console.log in first line):
return (
<ListItem onPress={() => { console.log("Navegando para o TXID:", tx_id); this.props.navigation.navigate("PersonalRank",{tx_id}); } } NoIndent style={{width: '100%', backgroundColor: 'white', marginTop: 0, paddingTop: 0, marginBottom: 0, paddingBottom: 5, marginLeft: 0, paddingLeft: 0, paddingRight: 0, marginRight: 0}}>
<Grid>
<Col size={10}>
<Row><Text style={{paddingLeft: 4}}>#{position}</Text></Row>
{temperature > 0 && settings.disp_temp == true &&
<Row><Text style={{fontSize: 10, paddingLeft: 4, color: temp_color, fontWeight: 'bold'}}>{temperature}ºC</Text></Row>
}
{speed > 0 &&
<Row><Text style={{fontSize: 10, paddingLeft: 4, fontWeight: 'bold'}}>{speed.toFixed(2)} Km/h</Text></Row>
}
</Col>
<Col size={90}>
<Row>
<Col size={67}><Text style={{alignSelf: 'flex-start', fontWeight: 'bold', fontSize: 14}}>{getPilotName(tx_id)} (ID:{tx_id})</Text></Col>
<Col size={33}><Text style={{fontSize: 10, alignSelf: 'flex-end', marginRight: 3}}>{translate('total_time',{...i18n_opt})}: {msToTime(total_time)}</Text></Col>
</Row>
<Row>
<Col><Text style={styles.itemHeaderText}>{translate('laps',{...i18n_opt})}</Text></Col>
<Col><Text style={styles.itemHeaderText}>{translate('last_lap',{...i18n_opt})}</Text></Col>
<Col><Text style={styles.itemHeaderText}>{translate('best_lap',{...i18n_opt})}</Text></Col>
<Col><Text style={styles.itemHeaderText}>{translate('best_time',{...i18n_opt})}</Text></Col>
<Col><Text style={[styles.itemHeaderText,styles.itemAlignRight, {marginRight: 5}]}>{translate('diff',{...i18n_opt})}</Text></Col>
</Row>
<Row>
<Col><Text style={styles.itemValueText}>{laps.length}</Text></Col>
<Col><Text style={[styles.itemValueText, {fontWeight: 'bold'}]}>{msToTime(last_time)}</Text></Col>
<Col><Text style={styles.itemValueText}>{best_lap}</Text></Col>
<Col><Text style={[styles.itemValueText, { color: '#90db18', fontWeight: 'bold' }]}>{msToTime(best_time)}</Text></Col>
<Col><Text style={[styles.itemValueText,styles.itemAlignRight, {marginRight: 5}]}>{diff}</Text></Col>
</Row>
{settings.show_estimate_pg &&
<Row>
<Col><ProgressBar style={styles.progress} progress={pg} width={null} /></Col>
</Row>
}
</Col>
</Grid>
</ListItem>
);
Now, in the PersonalRank screen, I'm getting tx_id in render() function...but sometime is doesn't work (even waiting for a while) (see video). The screen opens, but with the last opened tx_id.....sometimes it opens and after a few seconds, it changes.
render() {
const { pilots, settings } = this.props;
const { dispatchDelPilot } = this.props;
const tx_id = this.props.route.params.tx_id;
const name = getPilotName(tx_id);
const { dispatchDeleteLap, dispatchSplitLap } = this.props;
//console.log("ROUTE: ",this.props.route);
console.log("TX id (from props): ",tx_id);
const { race } = this.props;
let laps = [];
let pilot = "";
var laps_out = [];
var last_time = 0;
var fastest_time = 999999999;
var fastest_index = 0;
var last_speed = 0;
var total_time = 0;
for (var i = 0; i < race.length; i++) {
if (race[i].tx_id == tx_id) {
pilot = race[i].pilot;
laps = race[i].laps;
total_time = race[i].total_time;
break;
}
}
if (laps.length <= 0) {
//this.props.navigation.navigate("CurRace");
return <Text>No laps for this racer.</Text>;
}
let chart_values = [];
let chart_temperature_values = [];
let chart_labels = [];
let has_speed = false;
let has_bat = false;
//console.log("Laps: ",laps);
laps.map((item, index) => {
if (settings.show_pers_chart) {
chart_labels.push(index + 1);
chart_values.push(msToTime(item.time));
if ("temperature" in item) {
if (item.temperature > 0) {
}
}
}
if ("speed" in item) {
if (item.speed > 0) {
if (item.speed > last_speed) {
last_speed = item.speed;
}
has_speed = true;
}
}
if ("battery" in item) {
if (item.battery > 0) {
has_bat = true;
}
}
if (item.time > last_time) {
item.diff = "+" + msToTimeDiff(item.time - last_time);
item.diff_style = { color: "#ea2b12" };
last_time = item.time;
} else if (item.time < last_time) {
item.diff = "-" + msToTimeDiff(last_time - item.time);
item.diff_style = { color: "#12ea5a" };
last_time = item.time;
} else {
item.diff = "0";
last_time = item.time;
item.diff_style = null;
}
if (last_time < fastest_time) {
fastest_index = index;
fastest_time = last_time;
}
// Clear fastest style
item.fastest_style = null;
// Key
item.key = index.toString();
});
laps[0].diff = "0";
let has_temp = chart_temperature_values.length > 0 ? true : false;
laps[fastest_index].fastest_style = { color: "#12ea5a" };
const laps2 = laps;
const renderHiddenItem = (data, rowMap) => (
<View style={styles.rowBack}>
<Text>Left</Text>
<TouchableOpacity
style={[styles.backRightBtn, styles.backRightBtnLeft]}
onPress={() => {
this.setState({
...this.state,
visibleModal: true,
cur_tx_id: tx_id,
cur_lap: data.item.lap
});
}}
>
<MaterialCommunityIcons
name="arrow-split-vertical"
size={20}
color="#5e69d9"
/>
</TouchableOpacity>
<TouchableOpacity
style={[styles.backRightBtn, styles.backRightBtnRight]}
onPress={() => {
if (laps.length > 1) {
console.log("Data: ",data.item);
console.log("Apagando tx_id: ",tx_id,", Volta: ",data.item.lap,", Show alerts: ",settings.show_alerts);
dispatchDeleteLap(
tx_id,
data.item.lap,
settings.show_alerts
);
} else {
alert(
translate("cant_del_single_lap", { ...i18n_opt })
);
}
}}
>
<Icon active name="trash" size={20} />
</TouchableOpacity>
</View>
);
return (
<Container style={styles.container}>
<Header>
<Left>
<Button transparent onPress={() => this.props.navigation.goBack()}>
<Icon name="arrow-back" />
</Button>
</Left>
<Body>
<Title>{name}</Title>
</Body>
<Right>
<Button
transparent
onPress={() =>
this.props.navigation.navigate("RCharts", { tx_id })
}
>
<Icon type="MaterialCommunityIcons" name="chart-bar" />
</Button>
</Right>
</Header>
<Modal
isVisible={this.state.visibleModal === true}
animationIn={"slideInLeft"}
animationOut={"slideOutRight"}
>
{this._renderModalContent()}
</Modal>
<View style={{ flex: 1 }}>
<View style={{ height: "100%" }}>
<View style={{ flex: 8 }}>
<ScrollView>
<ViewShot ref="PersonalRankShot">
<View style={styles.line} collapsable={false}>
<View style={{ flex: 3 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("lap", { ...i18n_opt })}
</Text>
</View>
{chart_temperature_values.length > 0 && (
<View style={{ flex: 4 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("temperature", { ...i18n_opt })}
</Text>
</View>
)}
<View style={{ flex: 9 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("time", { ...i18n_opt })}
</Text>
</View>
{has_speed && (
<View style={{ flex: 4 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("speed", { ...i18n_opt })}
</Text>
</View>
)}
{has_bat && (
<View style={{ flex: 4 }}>
<Text style={[styles.alignCen, styles.headerTitle]}>
{translate("bat", { ...i18n_opt })}
</Text>
</View>
)}
<View style={{ flex: 4 }}>
<Text
style={[
{ paddingRight: 10 },
styles.alignRig,
styles.headerTitle
]}
>
{translate("diff", { ...i18n_opt })}
</Text>
</View>
</View>
<View style={styles.container}>
<SwipeListView
data={laps2}
disableRightSwipe={true}
renderItem={(data, rowMap) => (
<View style={styles.rowFront}>
<PersonalRankItem
dados={data.item}
settings={settings}
hasTemp={has_temp}
hasSpeed={has_speed}
hasBat={has_bat}
/>
</View>
)}
renderHiddenItem={renderHiddenItem}
leftOpenValue={75}
rightOpenValue={-90}
previewRowKey={'0'}
previewOpenValue={-40}
previewOpenDelay={3000}
//onRowDidOpen={onRowDidOpen}
/>
</View>
</ViewShot>
</ScrollView>
</View>
</View>
</View>
<TouchableOpacity
style={styles.bt_share}
onPress={() => this._captureChart()}
>
<Icon name="share" size={20} color="#ffffff" />
</TouchableOpacity>
</Container>
);
}
That am I missing here?
This used to work in old version of RN and react-navigation (4) without any problem.

TypeError: Can't read property 'map of undefined in React Native

I am doing react native project. I have array of data and in render method I am trying to looping it, Its like some custom tabbar. But, After loaded, I am trying to switching from one tab to another tab, Its throwing error and crashing like
TypeError: Can't read property 'map of undefined in React Native .
My code is
dashboard.js
constructor(props) {
super(props);
this.state = {
selectedIndex:0,
tabList:[
{tabName: ‘Telugu’, tabActiveImage:TeluguActiveImg, tabInactiveImage: TeluguInActiveImg, tabActiveText:'black', tabInactiveText: 'gray'},
{tabName: ‘Tamil’, tabActiveImage:TeluguActiveImg, tabInactiveImage: TeluguActiveImg, tabActiveText:'black', tabInactiveText: 'gray'},
{tabName: ’Hindi’, tabActiveImage: HindiActiveImg, tabInactiveImage: HindiInActiveImg, tabActiveText:'black', tabInactiveText: 'gray'},
{tabName: ‘English’, tabActiveImage: EnglishActiveImg, tabInactiveImage: EnglishInActiveImg, tabActiveText:'black', tabInactiveText: 'gray'},
]
}
}
OnTabItemHandler = (index) => {
this.setState({selectedIndex:index})
console.log('selected index is',index)
}
render(item) {
const {tabList} = this.state;
return (
<View>Some static data loading </View>
<View style = {styles.tabContainer}>
{
//loop throught the state
this.state.tabList.map((item,index)=>{
return(
//the style just to make it beautiful and easy to debug
<View style ={styles.tabInnerContainer}>
<TouchableOpacity style={styles.tabIcons}
//this onpress to handle of active selected tab
onPress={()=>this.OnTabItemHandler(index)}
>
<Image
//here's the magic show off
source={this.state.selectedIndex=index?item.tabActiveImage:item.tabInactiveImage}
style={styles.tabItemsImages}
/>
<Text style={styles.tabItemTextBlackColor}>{item.tabName}</Text>
</TouchableOpacity>
</View>
)
})
}
</View>
{this.renderBottomContent(item)}
</View>
);
}
}
and bottom view is
based on tab, I am changing the bottom view
renderBottomContent = (item) => {
this.state = { dataArray: getListData()}
switch(selectedTab) {
case "Telugu":
return <View style = {styles.flatListContainer}>
//show flat list data
}
ItemSeparatorComponent = {() => (
<View style={{height:15, backgroundColor:'blue'}/>
)}
/>
</View >
case "Tamil":
return <View style = {styles.bottomStaicScreensForTabs}>
<Text>
Tamil feature will come
</Text>
</View>
case "Hindi":
return <View style = {styles.bottomStaicScreensForTabs}>
<Text>
Hindi feature will come
</Text>
</View>
default:
return <View><Text></Text></View>
}
}
And also tab text colour not changing, always coming as black. Can
anyone help me, where I am doing wrong.
To better understand the problem I've created a snack. I'll post the code here in case it will no longer be available.
Note: it's not styled properly and images are not dynamic as you intend, but it can reproduce the question pretty well.
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
tabList: [
{
tabName: 'Telugu',
tabActiveImage: '',
tabInactiveImage: '',
tabActiveText: 'black',
tabInactiveText: 'gray',
},
{
tabName: 'Tamil',
tabActiveImage: '',
tabInactiveImage: '',
tabActiveText: 'black',
tabInactiveText: 'gray',
},
{
tabName: 'Hindi',
tabActiveImage: '',
tabInactiveImage: '',
tabActiveText: 'black',
tabInactiveText: 'gray',
},
{
tabName: 'English',
tabActiveImage: '',
tabInactiveImage: '',
tabActiveText: 'black',
tabInactiveText: 'gray',
},
],
};
}
onTabItemHandler = index => {
this.setState({ selectedIndex: index });
};
renderBottomContent = () => {
const { selectedIndex, tabList } = this.state;
const itemSelected = tabList[selectedIndex];
switch (itemSelected.tabName) {
case 'Telugu':
return (
<View style={{backgroundColor: 'yellow'}}>
<Text>Telugu feature will come</Text>
</View>
);
case 'Tamil':
return (
<View style={{backgroundColor: 'green'}}>
<Text>Tamil feature will come</Text>
</View>
);
case 'Hindi':
return (
<View style={{backgroundColor: 'cyan'}}>
<Text>Hindi feature will come</Text>
</View>
);
default:
return (
<View>
<Text>No content</Text>
</View>
);
}
};
render() {
const { tabList, selectedIndex } = this.state;
return (
<View style={styles.container}>
<Text>Some static data loading </Text>
<View style={styles.tabContainer}>
{//loop throught the state
tabList.map((item, index) => {
return (
//the style just to make it beautiful and easy to debug
<View style={styles.tabInnerContainer}>
<TouchableOpacity
style={styles.tabIcons}
//this onpress to handle of active selected tab
onPress={() => this.onTabItemHandler(index)}>
<Image
//here's the magic show off
source={
selectedIndex === index
? require('./assets/snack-icon.png')
: undefined
}
style={{ height: 30, width: 30 }}
/>
<Text
style={{
color:
selectedIndex === index
? item.tabActiveText
: item.tabInactiveText,
}}>
{item.tabName}
</Text>
</TouchableOpacity>
</View>
);
})}
</View>
{this.renderBottomContent()}
</View>
);
}
I'm still here for any clarification or improvement.
Update
Adding style like:
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'flex-start',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
tabContainer: {
flexDirection: 'row',
justifyContent: 'space-between',
},
});

Image is not displaying after loading from database?

I'm quite new to react native. I have a user with a profile picture that I'm trying to display on screen, when printing to my console snapshot.val().imageURL the correct url is displayed using this:
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => profImage = snapshot.val().imageURL)
.catch(error => console.log(error))
} else {
console.log("No user")
}
I assign that url to profImage and try to set my image uri to that variable:
<Avatar
large
rounded
source={{ uri: profImage }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
However, my image container remains blank. Does this have to do that when the render function is run, the url hasn't been retrieved thus the component display is blank? Do I use this.setState to update the component? If so, what is the proper way to do so?
Here is the relevant part of my component:
class Profile extends React.Component {
static navigationOptions = {
title: "Profile",
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0,
},
headerTitleStyle: {
color: 'black'
},
}
constructor() {
super();
this.state = {
image: null,
uploading: null,
}
}
render() {
const { navigate } = this.props.navigation;
let { image } = this.state;
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => profImage = snapshot.val().imageURL)
.catch(error => console.log(error))
} else {
console.log("No user")
}
return (
<ScrollView>
<View style={styles.profileTopContainer}>
<Avatar
large
rounded
source={{ uri: img }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
<Text
style={styles.usernameText}
>
Jordan Lewallen
</Text>
<TouchableHighlight
onPress={this._pickImage}>
<Text
style={styles.userMinutes}
>
Choose a profile picture
</Text>
</TouchableHighlight>
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
{
(image)
? <Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
: null
}
</View>
}
please try this
<Avatar
large
rounded
source={{ uri: img }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
replace with
<Avatar
large
rounded
source={{ uri: profImage }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
This is because you call the async request inside your render method itself.
When your render method execute you request for imageUrl which is a async call and takes time to resolve and update the value of profImage variable. At that time your render method is finished its execution and set the placeholder.
If you need this to be done you should keep this profImage in your component state. So once the state is updated your render method will be called again and update the UI with new image.
Try this!
class Profile extends React.Component {
static navigationOptions = {
title: "Profile",
headerStyle: {
backgroundColor: '#fff',
borderBottomWidth: 0,
},
headerTitleStyle: {
color: 'black'
},
}
constructor() {
super();
this.state = {
image: null,
uploading: null,
}
}
componentWillRecieveProps(nextProps){
var profImage = 'https://www.placeholdit.com';
var user = authFB.currentUser;
if (user) {
database.ref('users').child(user.uid).once('value')
.then((snapshot) => this.setState({ image: snapshot.val().imageURL }))
.catch(error => console.log(error))
} else {
this.setState({ image: 'https://www.placeholdit.com' });
console.log("No user")
}
}
render() {
const { navigate } = this.props.navigation;
let { image } = this.state;
return (
<ScrollView>
<View style={styles.profileTopContainer}>
<Avatar
large
rounded
source={{ uri: image }}
onPress={() => alert(profImage)}
activeOpacity={0.7}
containerStyle={{ marginBottom: 12 }}
/>
<Text
style={styles.usernameText}
>
Jordan Lewallen
</Text>
<TouchableHighlight
onPress={this._pickImage}>
<Text
style={styles.userMinutes}
>
Choose a profile picture
</Text>
</TouchableHighlight>
{this._maybeRenderImage()}
{this._maybeRenderUploadingOverlay()}
{
(image)
? <Image source={{ uri: image }} style={{ width: 250, height: 250 }} />
: null
}
</View>
}

Resources