Understanding React Natives setState and componentWillMount from FlatList - reactjs

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.

Related

How to create automatic Item Divider for React Native FlatLists?

I have a list of events that I am rendering in a FlatList. I would like there to be a divider whenever the event is on a different date - aka when {item.eventID.eventDate} for a given item is different to the one before it (I already know how to call the sever to return the dates in order).
Is there a way to autogenerate these dividers?
Here is my function for each item of the FlatList:
function Item({ item }) {
return (
<View>
<Text>{item.eventID.eventDate}</Text>
<Text>{item.eventID.artistName}</Text>
<Text>{item.ticketID}</Text>
</View>
);
}
And here is my class component for the page:
export default class MyEventsScreen extends Component {
state = {
tickets: [],
};
componentDidMount = () => {
fetch("http://127.0.0.1:8000/api/fullticket/", {
method: "GET",
})
.then((response) => response.json())
.then((responseJson) => {
this.setState({
tickets: responseJson,
});
})
.catch((error) => {
console.error(error);
});
};
render() {
return (
<View>
<FlatList
style={{ flex: 1 }}
data={this.state.tickets}
renderItem={({ item }) => <Item item={item} />}
keyExtractor={(item) => item.ticketID}
/>
</View>
);
}
}
You can write a custom function to render divider when the date value is changed.
The new Item function and renderDivider:
let prevDate = ""
function renderDivider(date) {
if(prevDate === "" || date !== prevDate) {
prevDate = date //initialize prevDate
return <Text style={styles.divider}>----{date}---</Text>
}
}
function Item({ item }) {
return (
<View>
{renderDivider(item.eventID.eventDate)}
<Text>{item.eventID.artistName}</Text>
</View>
);
}
const styles = StyleSheet.create({
divider: {
marginVertical: 15,
fontWeight: '700',
color: 'rgb(100,100,100)'
}
});

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

Can't find variable when adding data to Flatlist from json in React-Native

I'm new to react native, so please be kind! I am trying to populate a Flatlist using a JSON.
Below is my JSON data
{
"h1":{
"baseprice":899,
"description":"Upto Waist length Hair",
"imageUrl":"https://i.imgur.com/0IgYzAv.jpg'",
"price":799,
"time":"25 min",
"title":"Nourishing Hair Spa",
"type":"Service"
},
"h2":{
"baseprice":899,
"description":"Touch Up of length less than 4 inches",
"imageUrl":"https://i.imgur.com/q7ts4PZ.jpg",
"price":799,
"time":"45 min",
"title":"INOA Root Touch Up",
"type":"Service"
}
}
Here is the code that I used to push the JSON data in to my Flatlist
export default class Office extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: [],
};
}
componentDidMount() {
return fetch("https://stylmate1.firebaseio.com/hair.json")
.then((response) => response.json())
.then((responseJson) => {
this.setState({
isLoading: false,
dataSource: responseJson,
});
console.log(dataSource);
})
.catch((error) => {
console.error(error);
});
}
render() {
if (this.state.isLoading) {
return (
<View style={{ flex: 1, paddingTop: 20 }}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={(item) => <Text>{item.title}</Text>}
/>
</View>
);
}
}
As soon as I refresh the App I get an error
Can't find variable : dataSource
But if I console.log(responseJson); then I get the complete JSON object.
I don't know what am I doing wrong here. Please help me in fixing this.
Your FlatList is supposed to look like this:
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={({item}) => <Text>{item[1].title}</Text>}
/>
</View>
);
You have to destructure the item in order to use it.
Output:
Working Example code:
import React, { useState, useEffect } from 'react';
import { Text, View, FlatList, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { Card } from 'react-native-paper';
export default function App() {
const [dataSource, setData] = useState([]);
useEffect(() => {
fetch('https://stylmate1.firebaseio.com/hair.json')
.then((response) => response.json())
.then((responseJson) => {
setData(responseJson);
console.log(responseJson);
})
.catch((error) => {
console.error(error);
});
}, []);
return (
<View style={styles.container}>
{dataSource ? (
<FlatList
data={Object.entries(dataSource)}
renderItem={({ item }) => (
<View style={{ padding: 10 }}>
<Card>
<Text style={styles.paragraph}>{item[1].title}</Text>
</Card>
</View>
)}
/>
) : null}
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
paragraph: {
margin: 24,
fontSize: 18,
fontWeight: 'bold',
textAlign: 'center',
},
});
Expo Snack Live Demo
The Error happens because you are trying to log dataSource which is wrong it should be this.state.dataSource.
console.log(dataSource);
change it to
console.log(this.state.dataSource);
in your .then() block
firstly,
console.log(dataSource);
should change to
console.log(this.state.dataSource);
However, you may not get the correct console.log since set state is async. You get better and accurate answer with responseJson
Secondly, flatList requires an array of objects structure, you need to convert them using the following methods. I moved your "h1", and "h2" into the object and label them as key.
const original = {
"h1":{
"baseprice":899,
"description":"Upto Waist length Hair",
"imageUrl":"https://i.imgur.com/0IgYzAv.jpg'",
"price":799,
"time":"25 min",
"title":"Nourishing Hair Spa",
"type":"Service"
},
"h2":{
"baseprice":899,
"description":"Touch Up of length less than 4 inches",
"imageUrl":"https://i.imgur.com/q7ts4PZ.jpg",
"price":799,
"time":"45 min",
"title":"INOA Root Touch Up",
"type":"Service"
}
}
const newArray = Object.keys(original).map( key => ({...original[key], key }))
console.log(newArray)

React-Native setState not updating during fetch()

I have 3 records in my table, I can see the app fetches record to my remote because I console.log the response. My problem is that it will not display the item.
I know I defined correctly the column in FlatList because If I will set the per_page=1 which means pull 1 record every request. It will display but 2 records only will display the last record will not, if I set to per_page=30 nothing displays. is there a problem in my setState() during the response ?.I heard that setSate is not mutable..how can I apply the updater function of setsate in my code.?...I am still fresh on react native I hope someone will help me here.
I tried to do this but no luck!..also is this will matter that I use react-redux in my other page then in this screen I did not use only handling of state. ?...please help me react-native experts.
this.setState({
page: this.getParameterByName('page', res.next_page_url),
data: this.state.page === 1 ? res.data : [...this.state.data, ...res.data],
error: res.error || null,
loading: false,
refreshing: false,
last_page: res.last_page
},()=>{
return this.state;
});
Here is my complete code
import React, { Component } from 'react';
import {ScrollView, Text, View, Button, FlatList, ActivityIndicator} from 'react-native';
import { List, ListItem, Icon } from "react-native-elements";
import {connect} from "react-redux";
import numeral from "numeral";
import Moment from 'react-moment';
import moment from 'moment';
class Screen1 extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
data: [],
page: 1,
per_page: 30,
order_by:'id',
sort_by:'asc',
error: null,
refreshing: false,
param:'',
last_page:''
};
}
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteRequest = () => {
const {page, per_page,order_by,sort_by } = this.state;
const url = `http://myapp.com/api/mobile/credit?page=${page}&api_token=${this.props.token}&per_page=${per_page}&order_by=${order_by}&sort_by=${sort_by}`;
console.log("the url",url);
this.setState({ loading: true });
setTimeout(()=>{
fetch(url)
.then(res => res.json())
.then(res => {
console.log("the page is =",this.getParameterByName('page',res.next_page_url));
this.setState({
page:this.getParameterByName('page',res.next_page_url),
data: this.state.page === 1 ? res.data : [...this.state.data,...res.data],
error: res.error || null,
loading: false,
refreshing: false,
last_page: res.last_page
});
})
.catch(error => {
this.setState({ error, loading: false });
});
},1500);
};
handleRefresh = () => {
if( this.state.page) {
if (this.state.page <= this.state.last_page) {
this.setState(
{
refreshing: true,
page: this.state.page
},
() => {
this.makeRemoteRequest();
}
);
}
}
};
getParameterByName = (name,url) =>{
if (!url) url = window.location.href;
name = name.replace(/[\[\]]/g, "\\$&");
let regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"),
results = regex.exec(url);
if (!results) return null;
if (!results[2]) return '';
return parseInt(decodeURIComponent(results[2].replace(/\+/g, " ")), 10);
};
handleLoadMore = () => {
if( this.state.page){
if( this.state.page <= this.state.last_page ){
this.setState(
{
page: this.state.page
},
() => {
this.makeRemoteRequest();
}
);
}else{
console.log("cannot handle more",this.state.page)
}
}else{
console.log("page is null");
}
};
renderSeparator = () => {
return (
<View
style={{
height: 1,
width: "86%",
backgroundColor: "#CED0CE",
marginLeft: "14%"
}}
/>
);
};
renderHeader = () => {
return (
<View >
<Text h1
style={{
color: 'blue',
fontWeight: 'bold',
textAlign: 'center',
fontSize: 30,
backgroundColor: "#CED0CE",
}}
>{ numeral(this.props.thetotalcredit).format("#,##0.00") }</Text>
</View>
);
};
renderFooter = () => {
if (!this.state.loading) return null;
return (
<View
style={{
paddingVertical: 20,
borderTopWidth: 1,
borderColor: "#CED0CE"
}}
>
<ActivityIndicator animating size="large" />
</View>
);
};
render() {
return (
<FlatList
data={this.state.data}
keyExtractor = {(item, index) => index.toString()}
renderItem={({ item }) => (
<ListItem
title= { numeral(item.amountcredit).format("#,##0.00") }
subtitle= { moment(item.creditdate).format("MMM DD, YYYY") }
containerStyle={{ borderBottomWidth: 0 }}
/>
)}
extraData={this.state.data}
ItemSeparatorComponent={this.renderSeparator}
ListHeaderComponent={this.renderHeader}
ListFooterComponent={this.renderFooter}
refreshing={this.state.refreshing}
onRefresh={this.handleRefresh}
onEndReached={this.handleLoadMore}
onEndReachedThreshold={0.5}
stickyHeaderIndices={[0]}
/>
);
}
}
const mapStateToProps = (state) => {
return {
username: state.auth.username,
token:state.auth.token,
thetotalcredit:state.auth.total_credit
};
};
export default connect(mapStateToProps)(Screen1);

Getting "undefined" value for a variable outisde a fetch response in react native ?? Please?

/**
* Sample React Native App
* https://github.com/facebook/react-native
* #flow
*/
import React, {
Component,
} from 'react';
import {
AppRegistry,
Image,
ListView,
StyleSheet,
Text,
View,
} from 'react-native';
var REQUEST_URL = 'https://api.themoviedb.org/3/movie/popular?api_key=a667a62ffce29c5d1c5211e316ae43f6';
var REQUEST_URL_BASE_IMG = 'https://image.tmdb.org/t/p/w154/'
class Movies extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
};
var cast = "";
}
componentDidMount() {
this.fetchData(); //1st collection pulled
}
fetchData(){
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.results),
loaded: true,
});
})
.done();
}
render() {
if (!this.state.loaded) {
return this.renderLoadingView();
}
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderMovie}
style={styles.listView}
/>
);
}
renderLoadingView() {
return (
<View style={styles.container}>
<Text>
Loading movies...
</Text>
</View>
);
}
// fetchData2(movie){
// fetch('https://api.themoviedb.org/3/movie/'+movie.id+'/credits?api_key=a667a62ffce29c5d1c5211e316ae43f6')
// .then((response) => response.json())
// .then((responseJson) => {
// cast: responseJson.cast;
// })
// .catch((error) => {
// console.error(error);
// });
// }
renderMovie(movie,arr) {
var arr = [];
// I want the cast variable to be displayed on the screen. It is coming either undefined or prints "s1" which indicates no data.
fetch('https://api.themoviedb.org/3/movie/'+movie.id+'/credits?api_key=a667a62ffce29c5d1c5211e316ae43f6')
.then((response) => response.json())
.then((responseJson) => {
//cast: responseJson.cast;
//test = displaycast(responseJson.cast[0].name);
var cast = responseJson.cast[0].name;
console.log(cast);
})
.catch((error) => {
console.error(error);
});
// fetch('https://api.themoviedb.org/3/movie/'+movie.id+'/credits?api_key=a667a62ffce29c5d1c5211e316ae43f6')
// .then((response) => response.json())
// .then((responseJson) => {
// this.setState({
// cast: responseJson.cast,
// });
// })
// .catch((error) => {
// console.error(error);
// });
return (
<View style={styles.container}>
<Image
source={{uri: 'https://image.tmdb.org/t/p/w92/'+ movie.poster_path.replace(/\//g,"")}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{cast}</Text>
</View>
</View>
);
}
}
var styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
rightContainer: {
flex: 1,
},
title: {
fontSize: 20,
marginBottom: 8,
textAlign: 'center',
},
year: {
textAlign: 'center',
},
thumbnail: {
width: 53,
height: 81,
},
listView: {
paddingTop: 20,
backgroundColor: '#F5FCFF',
},
});
AppRegistry.registerComponent('Movies', () => Movies);
This is my code. My main concern is that I am getting the response from fetchData inside the renderMovie perfectly and storing it in cast variable. But if I try to access cast variable outside fetch. It shows undefined or empty string.
The entire point of not having this fetch with earlier is because I want to use the fetch response of 1st fetch Operation to get move.id and use it in the 2nd fetch request to get more details.
That's happening because JavaScript is asynchronous. I'd recommend doing something like your other fetch and set a loading state to know when you've fetched the cast and use that to render.

Resources