action is being dispatched before the state updates - reactjs

After authentication I am redirected to the dashboard page where I call 3 APIs as soon as I land and then I display the data on it. I am getting the siteId from authentication page and is available in redux store and set that siteId to dashboard page's state. The issue is my action is firing before my state gets the value from redux. MapStateToProps is firing the action with undefined. After the action is fired, the state is being set. If I hard code the state, it works. I tried to check if my state is set before the action is fired, but seems like my check is not working.
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
alarmCount: [],
workOrderCount: [],
etaWorkOrder: [],
countNum: true,
siteId: this.props.storeNum && this.props.storeNum.siteId
// siteId: 5260
}
}
componentDidMount() {
this.props.getOpenWorkOrdersCount();
this.props.getEtaWorkOrder();
}
componentWillReceiveProps({ alarmCount, workOrderCount, etaWO, storeNum }) {
if(alarmCount.code) {
this.setState({ alarmCount: alarmCount.code.data.ticketCount })
}
if(workOrderCount) {
this.setState({ workOrderCount: workOrderCount.data.event.woOutstanding })
}
if(etaWO) {
this.setState({ etaWorkOrder: etaWO.data.woList })
}
if(storeNum && storeNum.siteId && !(alarmCount.fetching || alarmCount.success) && !this.state.alarmCount.length){
this.props.getAlarmCount({siteId: storeNum.siteId});
}
}
const mapStateToProps = state => {
return {
alarmCount: state.alarmCount,
workOrderCount: state.workOrderCount.count,
etaWO: state.etaWorkOrder.count,
storeNum: state.ssoReducer.user
}
}
const mapDispatchToProps = dispatch => ({
getAlarmCount: data => dispatch(AlarmCountActions.getAlarmCount({ data }))
});
export default connect( mapStateToProps, mapDispatchToProps)(Dashboard);
This is the full component code:
class Dashboard extends Component {
constructor(props) {
super(props)
this.state = {
alarmCount: [],
workOrderCount: [],
etaWorkOrder: [],
countNum: true,
// siteId: props.storeNum && props.storeNum.siteId
siteId: ''
}
console.log('<<< storeNum', props.storeNum);
}
componentDidMount() {
// this.props.getOpenWorkOrdersCount();
// this.props.getEtaWorkOrder();
// if(this.props.storeNum){
// this.props.getAlarmCount(this.props.storeNum.siteId);
// }
}
// componentWillReceiveProps({ alarmCount, workOrderCount, etaWO, storeNum }) {
// if(alarmCount.code) {
// this.setState({ alarmCount: alarmCount.code.data.ticketCount })
// }
// if(workOrderCount) {
// this.setState({ workOrderCount: workOrderCount.data.event.woOutstanding })
// }
// if(etaWO) {
// this.setState({ etaWorkOrder: etaWO.data.woList })
// }
// if(storeNum){
// this.setState({ siteId: storeNum.siteId })
// }
// }
componentDidUpdate(prevProps) {
console.log('prevProps: ', prevProps)
if(prevProps.storeNum.siteId !== this.props.storeNum.siteId){
this.props.getAlarmCount(this.props.storeNum.siteId);
}
}
todaysDateFormatted = () => {
let today = new Date();
let dd = today.getDate();
let mm = today.getMonth() + 1;
let yyyy = today.getFullYear();
if (dd < 10) { dd = '0' + dd }
if (mm < 10) { mm = '0' + mm }
return today = mm + '-' + dd + '-' + yyyy;
}
howManyToday = data => {
let count = 0;
let todaysDate = this.todaysDateFormatted();
data.map(item => {
date = item.eta.replace(/(\d{4})\-(\d{2})\-(\d{2}).*/, '$2-$3-$1')
if(todaysDate == date) count++;
})
return count;
}
render() {
const navigation = this.props.navigation;
const woCount = this.state.workOrderCount
console.log(' ^^^^^^^ storeNuM: ', this.state.siteId)
return (
<View style={styles.container}>
<View style={styles.innerBox}>
<View style={styles.leftBox}>
<TouchableOpacity
style={styles.activeAlarms}
onPress={() => navigation.navigate(routes.ALARMS)}>
<View style={{ justifyContent: 'flex-end', flex: 1, background: 'transparent'}}>
{this.state.countNum ? (
<Fragment>
<Text style={[styles.blueColor, styles.font_24, ]}>{this.state.alarmCount}</Text>
</Fragment>
) : null}
<Text style={[styles.blueColor, styles.font_16] }>Active Alarms</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.openWorkOrders}
onPress={() => navigation.navigate(routes.WORK_ORDERS)}>
<View style={{ justifyContent: 'flex-end', flex: 1 }}>
<Text style={[styles.whiteColor, styles.font_24]}>{woCount}</Text>
<Text style={[styles.whiteColor, styles.font_16]}>Open{'\n'}Work Orders</Text>
</View>
</TouchableOpacity>
</View>
<View style={styles.rightBox}>
<TouchableOpacity
style={styles.todayWorkOrders}
onPress={() => navigation.navigate(routes.WORK_ORDERS)}
>
<View style={{ justifyContent: 'flex-end', flex: 1 }}>
<Text style={[styles.whiteColor, styles.font_24]}>{this.howManyToday(this.state.etaWorkOrder)}</Text>
<Text style={[styles.whiteColor, styles.font_16]}>Today ETA{'\n'}Work Orders</Text>
</View>
</TouchableOpacity>
<TouchableOpacity
style={styles.diyTrainingVideos}
onPress={() => navigation.navigate(routes.TRAINING_VIDEOS)}
>
<View style={{ justifyContent: 'flex-end', flex: 1 }}>
<Text style={[styles.blueColor, styles.font_16]}>DIY{'\n'}Training Videos</Text>
</View>
</TouchableOpacity>
</View>
</View>
<View style={styles.createWorkOrderBox}>
<TouchableOpacity
style={styles.createBtn}
onPress={() => navigation.navigate(routes.CREATE_WORK_ORDER)}>
<Text style={styles.workOrderText}>+ WORK ORDER</Text>
</TouchableOpacity>
</View>
</View>
)
}
}
const mapStateToProps = state => {
return {
alarmCount: state.alarmCount,
workOrderCount: state.workOrderCount.count,
etaWO: state.etaWorkOrder.count,
storeNum: state.ssoReducer.user
}
}
const mapDispatchToProps = dispatch => ({
getEtaWorkOrder: () => dispatch(etaWorkOrderActions.etaWorkOrder()),
getOpenWorkOrdersCount: () => dispatch(WorkOrderCountActions.count()),
getAlarmCount: data => dispatch(AlarmCountActions.getAlarmCount({ data })),
logoutUser: () => dispatch(logoutAction.logoutUser())
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Dashboard);
What is wrong with my check? ComponentWillReceiveProps is firing the action now, I had ComponentDidMount fire the action before, that did not work either. How can I make sure that my state is set before this.props.getAlarmCount({siteId: storeNum.siteId}) is fired?

Related

convert class components to functional components in react native?

I'm new to react native and I'm trying to upload multiple images using expo-image-picker-multiple but I'm having an error with the code so I'm trying to convert the class component to functional components so I can easily deal with it since I'm more familiar with functional components.
export default class ImageBrowse extends Component {
_getHeaderLoader = () => (
<ActivityIndicator size='small' color={'#0580FF'} />
);
imagesCallback = (callback) => {
const { navigation } = this.props;
this.props.navigation.setOptions({
headerRight: () => this._getHeaderLoader()
});
callback.then(async (photos) => {
const cPhotos = [];
for (let photo of photos) {
const pPhoto = await this._processImageAsync(photo.uri);
cPhotos.push({
uri: pPhoto.uri,
name: photo.filename,
type: 'image/jpg'
})
}
navigation.navigate('Main', { photos: cPhotos });
})
.catch((e) => console.log(e));
};
async _processImageAsync(uri) {
const file = await ImageManipulator.manipulateAsync(
uri,
[{ resize: { width: 1000 } }],
{ compress: 0.8, format: ImageManipulator.SaveFormat.JPEG }
);
return file;
};
_renderDoneButton = (count, onSubmit) => {
if (!count) return null;
return <TouchableOpacity title={'Done'} onPress={onSubmit}>
<Text onPress={onSubmit}>Done</Text>
</TouchableOpacity>
}
updateHandler = (count, onSubmit) => {
this.props.navigation.setOptions({
title: `Selected ${count} files`,
headerRight: () => this._renderDoneButton(count, onSubmit)
});
};
renderSelectedComponent = (number) => (
<View style={styles.countBadge}>
<Text style={styles.countBadgeText}>{number}</Text>
</View>
);
render() {
const emptyStayComponent = <Text style={styles.emptyStay}>Empty =(</Text>;
return (
<View style={[styles.flex, styles.container]}>
<ImageBrowser
max={4}
onChange={this.updateHandler}
callback={this.imagesCallback}
renderSelectedComponent={this.renderSelectedComponent}
emptyStayComponent={emptyStayComponent}
/>
</View>
);
}
}
Also this is related to the above code:
export default class MainScreen extends Component {
constructor(props) {
super(props)
this.state = {
photos: []
}
}
componentDidUpdate() {
const { params } = this.props.route;
if (params) {
const { photos } = params;
if (photos) this.setState({ photos });
delete params.photos;
}
}
renderImage(item, i) {
return (
<Image
style={{ height: 100, width: 100 }}
source={{ uri: item.uri }}
key={i}
/>
)
}
render() {
const { navigate } = this.props.navigation;
return (
<View style={{ flex: 1 }}>
<Button
title="Open image browser"
onPress={() => { navigate('ImageBrowser'); }}
/>
<ScrollView>
{this.state.photos.map((item, i) => this.renderImage(item, i))}
</ScrollView>
</View>
);
}
}
The error is when I browse the gallery to select images nothing is showing:
And this warning showing to me:
Also this is what i came up with when I tried to convert class to functional components:
const PropertiesInfo = ({ navigation }) => {
const [attachments, setAttachments] = useState([]);
const componentDidUpdate=()=> {
const { params } = attachments.route;
if (params) {
const { attachments } = params
if (attachments) setAttachments({ attachments })
delete params.attachments
}
};
const renderImage=(item, i) =>{
return (
<Image
style={{ height: 100, width: 100 }}
source={{ uri: item.uri }}
key={i}
/>
)
};
return (
<View style={{ flex: 1 }}>
<Button
title="Open image browser"
onPress={() => { navigate('ImageBrowser'); }}
/>
<ScrollView>
{attachments.map((item, i) => renderImage(item, i))}
</ScrollView>
</View>
);
};
export default PropertiesInfo;
And this also:
const ImageBrowse=({ navigation })=> {
_getHeaderLoader = () => (
<ActivityIndicator size='small' color={'#0580FF'} />
);
imagesCallback = (callback) => {
navigation.setOptions({
headerRight: () => _getHeaderLoader()
});
callback.then(async (attachments) => {
const cPhotos = [];
for (let photo of attachments) {
const pPhoto = await _processImageAsync(photo.uri);
cPhotos.push({
uri: pPhoto.uri,
name: photo.filename,
type: 'image/jpg'
})
}
navigation.navigate('Main', { attachments: cPhotos });
})
.catch((e) => console.log(e));
};
const _processImageAsync = async (uri) => {
const file = await ImageManipulator.manipulateAsync(
uri,
[{ resize: { width: 1000 } }],
{ compress: 0.8, format: ImageManipulator.SaveFormat.JPEG }
);
return file;
};
_renderDoneButton = (count, onSubmit) => {
if (!count) return null;
return <TouchableOpacity title={'Done'} onPress={onSubmit}>
<Text onPress={onSubmit}>Done</Text>
</TouchableOpacity>
}
updateHandler = (count, onSubmit) => {
navigation.setOptions({
title: `Selected ${count} files`,
headerRight: () => _renderDoneButton(count, onSubmit)
});
};
renderSelectedComponent = (number) => (
<View style={styles.countBadge}>
<Text style={styles.countBadgeText}>{number}</Text>
</View>
);
const emptyStayComponent = <Text style={styles.emptyStay}>Empty =(</Text>;
return (
<View style={[styles.flex, styles.container]}>
<ImageBrowser
max={4}
onChange={updateHandler}
callback={imagesCallback}
renderSelectedComponent={renderSelectedComponent}
emptyStayComponent={emptyStayComponent}
/>
</View>
);
};
export default ImageBrowse;

React Native Expo AV - Implementing SeekBar

I am attempting to use react-native-slider with Expo AV to create a seekbar, but am having trouble updating the 'value' state of slider. When I try to set it to currentPosition/durationPosition, it errors out, likely because initially these values are NaN. I CAN display current/duration however.
My best guess is that I need a way to wait until my mp3 is loaded before rendering the SeekBar. I probably also need to do a better job of separating components and keep PlayerScreen very minimal. I've messed around with this code so much I can barely remember what I've tried... Getting close to ditching Expo because react-native-track-player looks easier to work with and I've heard some bad things about Expo. Anyways, here's where I'm at now
export default class PlayerScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isPlaying: false,
playbackObject: null,
volume: 1.0,
isBuffering: false,
paused: true,
currentIndex: 0,
durationMillis: 1,
positionMillis:0,
sliderValue:0,
isSeeking:false,
}
}
async componentDidMount() {
try {
await Audio.setAudioModeAsync({
allowsRecordingIOS: false,
interruptionModeIOS: Audio.INTERRUPTION_MODE_IOS_DO_NOT_MIX,
playsInSilentModeIOS: true,
interruptionModeAndroid: Audio.INTERRUPTION_MODE_ANDROID_DUCK_OTHERS,
shouldDuckAndroid: true,
staysActiveInBackground: true,
playThroughEarpieceAndroid: true
})
this.loadAudio()
} catch (e) {
console.log(e)
}
}
async loadAudio() {
const { currentIndex, isPlaying, volume} = this.state
try {
const playbackObject = new Audio.Sound()
const source = {
uri: this.props.route.params.item.uri
}
const status = {
shouldPlay: isPlaying,
volume,
}
playbackObject.setOnPlaybackStatusUpdate(this.onPlaybackStatusUpdate)
await playbackObject.loadAsync(source, status, true)
this.setState({playbackObject})
var sliderValue = this.state.positionMillis/this.state.durationMillis
} catch (e) {
console.log(e)
}
}
handlePlayPause = async () => {
const { isPlaying, playbackObject } = this.state
isPlaying ? await playbackObject.pauseAsync() : await playbackObject.playAsync()
this.setState({
isPlaying: !isPlaying
})
}
onPlaybackStatusUpdate = status => {
this.setState({
isBuffering: status.isBuffering,
durationMillis: status.durationMillis,
positionMillis: status.positionMillis,
})
}
render() {
const { item } = this.props.route.params;
return (
<View style={globalStyles.container}>
<Header />
<View style={globalStyles.subHeader}>
<Text style={globalStyles.title}>{ item.title }</Text>
</View>
<View style={styles.text}>
<Text>{ item.text }</Text>
</View>
<SeekBar
durationMillis={this.state.durationMillis}
positionMillis={this.state.positionMillis}
sliderValue={this.state.sliderValue}
/>
And here's the SeekBar component:
const SeekBar = ({
positionMillis,
durationMillis,
sliderValue
}) => {
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }} />
<Text style={[styles.text, { width: 40 }]}>
{positionMillis + ' / ' + durationMillis}
</Text>
</View>
<Slider
minimumValue={0}
maximumValue={1}
value={sliderValue}
style={styles.slider}
minimumTrackTintColor='#fff'
maximumTrackTintColor='rgba(255, 255, 255, 0.14)'
/>
</View>
);
};
export default SeekBar;
put
<SeekBar
durationMillis={this.state.durationMillis}
positionMillis={this.state.positionMillis}
sliderValue={this.state.sliderValue}
/>
in the screen component and
const SeekBar = ({
positionMillis,
durationMillis,
sliderValue
}) => {
sliderValue = positionMillis/durationMillis
return (
<View style={styles.container}>
<View style={{ flexDirection: 'row' }}>
<View style={{ flex: 1 }} />
in the SeekBar component

Passing props to child doesn't make him update - React Native

I am writing because I can't figure why one of the two childs Graph.js won't update after I udpate the state of the parent Data.js (throught a "lift up" via the second child Bouton.js).
I feel giga dumb and it's been now hours, I'm desperate...
I am trying to display charts with buttons above to choose a period of time for the chart (day, week, month). Clicking the button can change the state of the parent but I can't make the child Graph to update. I know I am doing something wrong.
Parent: Data.js
export default class Data extends React.Component {
constructor(props) {
super(props);
this.state = { periode: "Jour" };
}
handleClick(p) {
this.setState({
periode: p
});
}
render() {
console.log(this.state);
return (
<View>
<Boutons
onClick={res => this.handleClick(res)}
cursor={this.state.periode}
/>
<Graph periode={this.state.periode} dataType="temp" />
<Graph periode={this.state.periode} dataType="press" />
</View>
);
}
}
Child 1 (everything seems fine)
export default class Boutons extends React.Component {
constructor(props) {
super(props);
}
_getNextEntryTime() {
var d = new Date();
var h = d.getHours();
var m = d.getMinutes();
var res;
if (m >= 30) {
res = (h + 1).toString() + ":00";
} else {
res = h.toString() + ":30";
}
return res;
}
//Gestion de la selection des boutons
_boutonStyle(periode) {
if (this.props.cursor == periode) {
return {
// backgroundColor: "#9c9c9c",
borderBottomWidth: 3,
borderColor: "#728FB5",
width: Dimensions.get("window").width / 3 - 10,
height: 30,
alignItems: "center",
justifyContent: "center"
};
} else {
return {
backgroundColor: "#dfdfdf",
width: Dimensions.get("window").width / 3 - 10,
height: 30,
borderRadius: 2,
alignItems: "center",
justifyContent: "center"
};
}
}
_textStyle(periode) {
if (this.props.cursor == periode) {
return { color: "#728FB5" };
} else {
return { color: "black" };
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.container_top}>
<View style={styles.rect}>
<Text style={styles.text_top}>
Prochain relevé: {`\n`}
<Text style={styles.numbers}>{this._getNextEntryTime()}</Text>
</Text>
</View>
<Single />
</View>
<View style={styles.container_buttons}>
<TouchableOpacity
style={this._boutonStyle("Jour")}
onPress={() => this.props.onClick("Jour")}
>
<Text style={this._textStyle("Jour")}>Jour</Text>
</TouchableOpacity>
<TouchableOpacity
style={this._boutonStyle("Semaine")}
onPress={() => this.props.onClick("Semaine")}
>
<Text style={this._textStyle("Semaine")}>Semaine</Text>
</TouchableOpacity>
<TouchableOpacity
style={this._boutonStyle("Mois")}
onPress={() => this.props.onClick("Mois")}
>
<Text style={this._textStyle("Mois")}>Mois</Text>
</TouchableOpacity>
</View>
</View>
);
}
}
Graph.js Child 2 that won't update, nothing is happening
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [], format_const: null };
}
// Chargement de la page et recherche des entrys
componentDidMount() {
const entrys = getEntry(this.props.periode);
entrys.then(reponse => {
reponse.map(donnee => {
this.setState({
data: this.state.data.concat(donnee[this.props.dataType])
});
});
this.setState({
format_const: Config.CHART[this.props.dataType],
isLoading: false
});
});
}
// Affichage du loading
_displayLoading() {
if (this.state.isLoading) {
return (
<View style={styles.loading_container}>
<ActivityIndicator size="large" />
</View>
);
}
}
_displayChart() {
return (
<LineChart
data={{
datasets: [
{
data: this.state.data,
strokeWidth: 2 // optional
}
],
legend: [this.state.format_const["label"]]
}}
width={Dimensions.get("window").width - 10} // from react-native
height={220}
withInnerLines={false}
yAxisSuffix={this.state.format_const["alert"]}
onDataPointClick={({ value, dataset, getColor }) =>
Alert.alert(`${value}` + this.state.format_const["alert"])
}
chartConfig={{
backgroundGradientFrom: this.state.format_const["color"],
backgroundGradientTo: this.state.format_const["color"],
decimalPlaces: 0, // optional, defaults to 2dp
color: (opacity = 1) => `rgba(255, 255, 255, ${opacity})`,
style: {
borderRadius: 16
},
propsForDots: {
r: "2"
}
}}
bezier
style={{
marginVertical: 10,
borderRadius: 16
}}
/>
);
}
render() {
if (!this.state.isLoading) {
return <View>{this._displayChart()}</View>;
} else {
return <View>{this._displayLoading()}</View>;
}
}
}
It appears that your only use of the prop periode is in the componentDidMount method of Graph. So at mount time, Graph reads what the prop is, and then sets the state, which is used in the Graph render method. But when the parent component changes its state, and the new value for this.state.periode is passed as a prop to Graph, Graph doesnt doesn't necessarily know what to do with this updated information. So you'll need to use a componentDidUpdate statement to read new props coming in from the parent's state:
componentDidUpdate(prevProps){
if (prevProps.periode !== this.props.periode){
const entrys = getEntry(this.props.periode);
entrys.then(reponse => {
reponse.map(donnee => {
this.setState({
data: this.state.data.concat(donnee[this.props.dataType])
});
});
});
}
}
I'm assuming you want the same thing to happen in `componentDidMount` as is happening in `componentDidUpdate`, but you may need to change the code within `componentDidUpdate` to whatever you need.

Understanding React Natives setState and componentWillMount from FlatList

So I'm trying to make a simple application with expo and expo audio that will generate a list of audio buttons and text. But I cannot figure out how react works regarding redrawing the setState OUTSIDE componentWillMount and how to remake a soundobject with a new URI
So right now it will work but only playing the FIRST uri, I assume this is because the object still exists.
And it will not change the state of the button, I know this is because react cant see its changing for some reason from FlatList
It works outside of it, if I only make one button in renders view.
FlatList will render the setStates if I use LegacyImplementation=true .. But Im warned this is deprecated. And it renders it for all buttons at the same time
This is my handlerClass:
export class TSSGetter extends React.Component {
constructor(props){
super(props);
this.state ={
isLoading: true,
playingStatus: "Play"
}
}
retrieveData() {
const endpoint = 'http://127.0.0.1:3333/get'
const data = {
"userId": "123412341234",
"hmac": "detteerikkeenrigtighmac"
}
return new Promise((resolve, reject) => {
fetch(endpoint, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type':'application/json'
},
body: JSON.stringify(data)
})
.then((resp) => {
console.log('hej return')
return resp.json();
})
.then((resp) => {
resolve(resp);
console.log('resp')
}).catch(function(error) {
console.log(error,'naeh')
});
});
}
componentDidMount(){
this.retrieveData()
.then((resp) => {
var pages = resp.books.contentObjects
pages.map((userData) => {
console.log('superduper pages', userData.contentObjectId)
})
this.setState({
isLoading: false,
dataSource: resp.books.contentObjects,
dataroot: resp.books
});
}).catch((err) => {
//handle error
console.log("Api call error2");
alert(err);
})
}
async _playRecording(AudioURL) {
console.log(AudioURL)
const { sound } = await Audio.Sound.createAsync(
{uri: AudioURL},
{
shouldPlay: true,
isLooping: true,
},
this._updateScreenForSoundStatus,
);
this.sound = sound;
this.setState({
playingStatus: 'playing'
});
}
_updateScreenForSoundStatus = (status) => {
if (status.isPlaying && this.state.playingStatus !== "playing") {
this.setState({ playingStatus: "playing" });
} else if (!status.isPlaying && this.state.playingStatus === "playing") {
this.setState({ playingStatus: "donepause" });
}
};
async _pauseAndPlayRecording() {
if (this.sound != null) {
if (this.state.playingStatus == 'playing') {
console.log('pausing...');
await this.sound.pauseAsync();
console.log('paused!');
this.setState({
playingStatus: 'donepause',
});
} else {
console.log('playing...');
await this.sound.playAsync();
console.log('playing!');
this.setState({
playingStatus: 'playing',
});
}
}
}
_syncPauseAndPlayRecording() {
if (this.sound != null) {
if (this.state.playingStatus == 'playing') {
this.sound.pauseAsync();
} else {
this.sound.playAsync();
}
}
}
_playAndPause = (AudioURL) => {
console.log(AudioURL)
switch (this.state.playingStatus) {
case 'Play':
this._playRecording(AudioURL);
break;
case 'donepause':
case 'playing':
this._pauseAndPlayRecording();
break;
}
}
render(){
if(this.state.isLoading){
return(
<View style={{flex: 1, padding: 20}}>
<ActivityIndicator/>
</View>
)
}
const styling = {
flex: 1,
paddingTop:10
// flexDirection: 'row'
}
const data = this.state.dataroot;
return(
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={({item}) =>
<View>
<TouchableOpacity style={styles.button} onPress={() => this._playAndPause(item.AudioURL)}>
<Text style={styles.buttonText}>
{this.state.playingStatus}+ {item.contentObjectId}
</Text>
</TouchableOpacity>
<Text style={styles.description}>
{item.text},
</Text>
</View>
}
keyExtractor={(item, index) => item.contentObjectId}
/>
</View>
);
}
}
UPDATE: setting extraData={this.state} in flatlist updates the button.. But all the buttons. How do I change the scope of the button?
You could create a specific component for the items in the FlatList. Each of the items will then have their own state.
import React, { Component } from "react";
import { StyleSheet, Text, View } from "react-native";
import { FlatList } from "react-native-gesture-handler";
export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<FlatList
keyExtractor={(item, index) => index.toString()}
data={[1, 2, 3, 4, 5]}
renderItem={({ item }) => <Sound />}
/>
</View>
);
}
}
class Sound extends Component {
constructor() {
super();
this.state = {
status: "IDLE"
};
}
onChangeState = value => {
this.setState({
status: value
});
};
render() {
const { status } = this.state;
return (
<View style={{width: 200,paddingVertical: 10}}>
<Text>Status: {status}</Text>
<View style={{ flex: 1,flexDirection: "row", justifyContent: "space-between" }}>
<Text onPress={() => this.onChangeState("PLAYING")}>PLAY</Text>
<Text onPress={() => this.onChangeState("STOPPED")}>STOP</Text>
<Text onPress={() => this.onChangeState("PAUSED")}>PAUSE</Text>
</View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 100,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center"
}
});
I checked out in the docs, here, and I saw that it will re-render just if you pass the state prop, see this explanations:
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.

setState when fetch data that leads Render in loop

The problem is when i create paginaion button, the render is running in loop.
As i call api from external that i can store all data in dataset. I know something went wrong but i am not able to solve it personally, so can any one suggest solutions to help improve? Thanks.
SearchScreen.js
componentDidMount() {
this.setState({isLoading: true},function(){
this.fetchData(this.url)
});
}
async fetchData(query, pageNo) {
try {
let response = await fetch(query);
let responseText = await response.json();
let json = await this.getData(responseText, pageNo);
} catch(error) {
console.error(error);
}
}
async getData(responseText, pageNo){
this.setState({
data: responseText.response.listings,
isLoading: false,
currentPage: pageNo,
lastPage: responseText.response.total_pages
});
}
PropertyList(arr, navigate) {
return arr.map(function(property, i){
return(
<TouchableHighlight style={styles.button} underlayColor='#99d9f4'
key={i} onPress={() => navigate('PropertyView', {property: property})} >
<View key={i} style={styles.rowContainer}>
<Image style={styles.thumb} source={{ uri: property.img_url }} />
<View style={styles.textContainer}>
<Text style={styles.price}>{property.price_formatted}</Text>
<Text style={styles.title}
numberOfLines={2}>{property.title}</Text>
</View>
</View>
</TouchableHighlight>
);
});
}
SetPage = (pageNo) =>{
this.url = this.GetChangedUrl(pageNo);
this.fetchData(this.url, pageNo);
}
GetChangedUrl = (pageNo) =>{
return this.url.replace(/(page=\d+&)/g, 'page='+pageNo+ '&');
}
_formArray = (pageNo) =>{
if (pageNo == 1){
return [pageNo, pageNo+1, pageNo+2];
}else if (pageNo == this.lastPage){
return [pageNo-2, pageNo-1, pageNo];
}else{
return [pageNo-1, pageNo, pageNo+1];
}
}
_createPageButton = (currentPage) =>{
var array = this._formArray(currentPage);
var btnBody = array.map( (item, i) => {
return (
<TouchableHighlight style={styles.pageButton} key={i} Onpress={this.SetPage(i)}>
<Text style={styles.text} >{i + 1}</Text>
</TouchableHighlight>
)
});
return btnBody;
}
render() {
const { navigate } = this.props.navigation;
const { params } = this.props.navigation.state;
this.url = params.url;
let propertyList;
if (this.state.isLoading){
propertyList = (
<ActivityIndicator size={108} style= {styles.indicator}/>
)
}else{
propertyList = (
<View >
<View >
<ScrollView >
{this.PropertyList(this.state.data, navigate)}
<View style={styles.separator}/>
<View style={styles.pageContainer}>
{this._createPageButton(this.currentPage)}
</View>
</ScrollView>
</View>
</View>
)
}
return (
<View>
{propertyList}
</View>
)
}
}
Whole Code from here
I believe this is caused by:
Onpress={this.SetPage(i)}
which should be changed to:
Onpress={() => this.SetPage(i)}
Your code calls setPage when rendering and passes to onPress the result of the call, causing change of page, and therefore a new load, on every render.

Resources