redux-saga used with redux causes render() to be called twice - reactjs

I have a problem while incorporating the redux saga in my application .
What I understand from the tutorials is that the middleware will parse the action dispatched from the app and will process some async operations(asycn storage, api).
Then put another action in the pipeline which will trigget the reducer and then updates the state.
Flow is that my component button click triggers an action dispatch which is caught by the watcher in the saga, and then api calls are processed then the put is done to send data to reducer to update the state.
If in my component I dispatch the action FETCH_DATA, the saga is catching that and processing the data fetch and then calling the updateprofile. This calls the reducer and processing happens.
This is what I expected. But even before hitting the saga FETCH_DATA, the call comes to the reducer and since there is no action type FETCH_DATA case to handle it will return the default state and thiscauses the app to re render. So rendering happens twice and it is causing some problem to my items in the list.
I think this is also somewhat expected as I read in some article .How to get rid of this re rendering ?
function* datafetch(action) {
let { data } = yield call(loginApi, action.payload);
yield put(updateProfile(data.profile));
}
export function* dataFetcherSaga() {
yield takeLatest('FETCH_DATA', datafetch);
}
/reducer.js
const toDoListReducer = (state, action) => {
switch (action.type) {
case "UPDATE_APPREDUX_STATE":
return {
//some processing with data and state
};
break;
case "UPDATE_PROFILE":
return {
//some processing with data and state
};
break;
default:
return state;
}
return state;
};
export default toDoListReducer;
//action
export const fetchData = currentDay => {
return {
type: 'FETCH_DATA',
currentDay: currentDay
};
};
export function updateProfile(profile) {
return { type: 'UPDATE_PROFILE', payload: authParams };
}
//component
render(){
return (
<View style={styles.viewStyle}>
<SafeAreaView>
<View style={styles.viewPadding}>
<View>
<View style={styles.toDoViewStyle}>
<TextInput
style={styles.toDoInputStyle}
placeholder="What you gonna do ?"
onChangeText={text => {
this.newTask = text;
}}
/>
<TouchableOpacity
onPress={() => {
this.props.updateTasks(
{
taskID: new Date().getTime(),
taskDay: this.currentDay, //millisecond field to get a unique value
taskValue: this.newTask,
taskCompleted: false,
taskCompletedTime: null
},
"addTask"
);
}}
>
<Image
style={styles.addImage}
source={require("../../assets/add.png")}
/>
</TouchableOpacity>
</View>
<Text>
↓ To Do Items ↓
</Text>
<SectionList
style={styles.flatListStyle}
renderItem={({ item }) => <ToDoListItem value={item} />}
renderSectionHeader={({ section: { title, data } }) => {
if (data.length > 0) {
return (
<Text
style={{
paddingTop: 5,
fontWeight: "bold",
fontStyle: "italic",
fontSize: 15,
color: title === "Completed Tasks:" ? "green" : "red",
textDecorationLine: "underline"
}}
>
{title}
</Text>
);
}
}}
stickySectionHeadersEnabled={false}
sections={[
{
title: "Completed Tasks:",
data: this.props.tasks.filter(tasks => {
return tasks.taskCompleted === true;
})
},
{
title: "InComplete Tasks:",
data: this.props.tasks.filter(tasks => {
return tasks.taskCompleted === false;
})
},
,
]}
keyExtractor={(item, index) => item + index}
/>
</View>
</View>
</SafeAreaView>
</View>
);}
//child item
class ToDoListItem extends React.Component {
constructor(props) {
super(props);
//this.state = { checked: false };
}
selectItem = () => {
let updatedObject = {
taskID: this.props.value.taskID,
taskCompleted: !this.props.value.taskCompleted,
};
this.props.done(updatedObject);
};
deleteItem = () => {
let deletedObject = {
taskID: this.props.value.taskID,
};
this.props.delete(deletedObject);
};
render() {
return (
<View style={styles.viewStyle}>
<View style={styles.checkBoxStyle}>
<CheckBox
checkedCheckBoxColor="green"
onClick={this.selectItem}
isChecked={this.props.value.taskCompleted}/>
</View>
<View style={styles.inputTextViewStyle}>
<Text
style={
this.props.value.taskCompleted
? styles.completeDone
: styles.inComplete
}>
{this.props.value.taskValue}
</Text>
{this.props.value.taskCompleted && <Text style={{ fontSize: 11, fontStyle: "italic", color: "blue" }}>{"\n"}{"Completed # " + this.props.value.taskCompletedTime}</Text>}
</View>
<View style={styles.deleteTextStyle}>
<TouchableOpacity onPress={this.deleteItem}>
<Image
style={styles.deleteImage}
source={require('../../assets/delete.png')}
/>
</TouchableOpacity>
</View>
</View>
);
}
}

Yes this is expected behavior when you dispatch any action it will go to reducer first instead of any middleware. Best practice is to show the loading screen as mentioned but if you really want to hold your render then you can use shouldComponentUpdate() Component Lifecycle Method which takes two params nextProps and nextState. This method basically hold the re-render which can be achieved by a simple if condition for e.g.
shouldComponentUpdate (nextProps, nextState) {
let shouldUpdate = true
if ((nextProps.someProps === this.props.someProps )) {
shouldUpdate = false
}
return shouldUpdate
}

To prevent re-rendering, you could create a container for the component. In this container component, use the redux compose method and connect method from react-redux, e.g
const ContainerComponent = compose(
connect(mapStateToProps),
component-that-displays-on-loading
)(Component-that-needs-data-from-the-state);
compose is a higher order component that runs passed-in functions from right-to-left. This pattern skips the first render and executes the flow as expected. Hope this helps.

Related

[Unhandled promise rejection: TypeError: undefined is not an object (evaluating 'currentUser.uid')]

i get this warning when i try to store details to firebase using the currently logged in user.
i followed a online tutorial but i get this problem, is there any alternate method to use to achieve this?
i read about this problem but couldn't find a fix, i saw that its due to the user not being logged in but i did log in
var currentUser
class DecodeScreen extends Component {
addToBorrow = async (booktitle, bookauthor, bookpublisher, bookisbn) => {
currentUser = await firebase.auth().currentUser
var databaseRef = await firebase.database().ref(currentUser.uid).child('BorrowedBooks').push()
databaseRef.set({
'title': booktitle,
'author': bookauthor,
'publisher': bookpublisher,
'isbn': bookisbn
})
}
state = {
data: this.props.navigation.getParam("data", "NO-QR"),
bookData: '',
bookFound: false
}
_isMounted = false
bookSearch = () => {
query = `https://librarydb-19b20.firebaseio.com/books/${9781899606047}.json`,
axios.get(query)
.then((response) => {
const data = response.data ? response.data : false
console.log(data)
if (this._isMounted){
this.setState({ bookData: data, bookFound: true })
}
})
}
renderContent = () => {
if (this.state.bookFound) {
return(
<View style={styles.container2}>
<View style={styles.htext}>
<TextH3>Title :</TextH3>
<TextH4>{this.state.bookData.title}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Author :</TextH3>
<TextH4>{this.state.bookData.author}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Publisher :</TextH3>
<TextH4>{this.state.bookData.publisher}</TextH4>
</View>
<View style={styles.htext}>
<TextH3>Isbn :</TextH3>
<TextH4>{this.state.bookData.isbn}</TextH4>
</View>
</View>
)
}
else {
return(
<View style={styles.loading}>
<ActivityIndicator color="blue" size="large" />
</View>
)
}
}
componentDidMount(){
this._isMounted = true
}
componentWillUnmount(){
this._isMounted = false
}
render() {
{this.bookSearch()}
return (
<View style={styles.container}>
{this.renderContent()}
<Button title='Borrow' onPress={() => this.addToBorrow(this.state.bookData.title, this.state.bookData.author, this.state.bookData.publisher, this.state.bookData.isbn)} />
</View>
);
}
}
First, firebase.auth().currentUser is not a promise, and you can't await it. It's either null or a user object.
Second, it's not guaranteed to be populated with a user object on immediate page load. You will need to use an auth state observer to know when the user object first becomes available after a page load.

why i got [object object ]

import React, { Component } from "react";
import { Button, View ,Text ,StyleSheet, FlatList, ScrollView} from "react-native";
import DateTimePicker from "react-native-modal-datetime-picker";
import moment from 'moment'
import addDays from 'date-fns/addDays'
import Modal from 'react-native-modal';
export default class MExample extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
day:[],
isDateTimePickerVisible: false,
choseDate:'',
visibleModal: false,
lists:''
};
}
showDateTimePicker = () => {
this.setState({ isDateTimePickerVisible: true });
};
hideDateTimePicker = () => {
this.setState({ isDateTimePickerVisible: false });
};
handleDatePicker = ( date ) => {
// console.log("A date has been picked:", date); here date come correctly
this.setState ({
choseDate: 'Subscription start date ' + moment(date).format('MMMM, Do YYYY '),
})
this.hideDateTimePicker();
};
hideListPicker = () => {
this.setState({ visibleModal: null ,list:[] });
};
handleListPicker = ( list ) => {
console.log(list.toString())
this.setState ({
lists: 'list of start dates ' + list
})
this.hideListPicker();
};
getListViewItem = (item) => {
let newList = this.state.list;
if (newList.includes(item)) {
let index = newList.indexOf(item);
newList.splice(index,1);
} else {
newList.push(item);
}
this.setState({
list: newList,
});
}
renderModalContent = () => (
<View>
<Text style={styles.textBox} onPress={this.showDateTimePicker}>Select Date</Text>
<DateTimePicker
isVisible={this.state.isDateTimePickerVisible}
onConfirm={this.handleDatePicker}
onCancel={this.hideDateTimePicker}
minimumDate = {new Date()}
maximumDate = {addDays(new Date(), 30)}
/>
<View style = {{backgroundColor:'white'}}>
<View>
<FlatList horizontal={true}
data = {[{day: '1'},{day: '2'}, {day: '3'},{day: '4'}, {day: '5'},{day: '6'},{day: '7'}]}
renderItem={({item, index}) =>
<Text style={styles.textBox} key={index}
onPress={this.getListViewItem.bind(this, item.day)}>{item.day}
</Text>}
/>
<ScrollView
style = {{marginHorizontal: 20}}
horizontal={true}
>
{
this.state.list.map((l, index) => {
return(
index !== this.state.list.length - 1 ? <Text style={{fontSize:30, color:'red'}}>{l}, </Text> : <Text style={{fontSize:30, color:'red'}}>{l}</Text>
)
})
}
</ScrollView>
</View>
</View>
<Button
onPress={this.handleListPicker}
title="Submit"
/>
</View>
);
render() {
return (
<>
<Text style={{fontSize:20}}>Frequency</Text>
<View style={styles.container} >
<Text style={styles.textBox} onPress={() => this.setState({ visibleModal: 'default' })}>Weekly </Text>
</View>
<Text style={{fontSize:20, color:'black', textAlign:'center'}}>{this.state.choseDate} </Text>
<Text style={{fontSize:20, color:'black', textAlign:'center'}}>{this.state.lists} </Text>
<Modal isVisible={this.state.visibleModal === 'default'}
onBackButtonPress={() => this.setState({ visibleModal: null, list:[] }, )}>
{this.renderModalContent()}
</Modal>
</>
);
}
}
const styles = StyleSheet.create({
container:{
flexDirection:'row',
flexWrap:'wrap',
justifyContent:'center',
},
textBox :{
fontSize:20,
textAlign:'center',
borderWidth: 1,
borderRadius: 12,
borderColor: "#CBCBCB",
margin:10,
padding:5,
backgroundColor:'#a0a3a3'
},
});
i have created modal here user select date list and after submit i clear list in setState
why i get [object object] in console
export default class MExample extends Component {
constructor(props) {
super(props);
this.state = {
list: [],
visibleModal: false,
lists: ''
};
}
hideListPicker = () => {
this.setState({ visibleModal: null ,list:[] });
};
handleListPicker = ( list ) => {
console.log(list.toString())
// [object objcet]
this.setState ({
lists: 'list of start dates ' + list
})
this.hideListPicker();
};
render(){
return(
// jsx <Text>{this.state.lists} </Text> // [object object]
<Button onPress={this.handleListPicker}
title="Submit"
/>
)
}
Use JSON.stringify instead .toString
This question might help you What's the difference in using toString() compared to JSON.stringify()? understand the difference
let x = {a: 123, b: 321};
console.log("toString", x.toString());
console.log("stringified", JSON.stringify(x));
If you're having a circular JSON you might like to visit How can I print a circular structure in a JSON-like format? to see how to console such JSONs
It is because you are passing event as parameter on Button click, which is an object. Now when you use list.toString(), it will convert this event Object into String and show you [Object Object].
You can verify it by using
console.log("data = ",list)
instead of
console.log(list.toString())
In your case, just remove the toString() function to get what you need.
SIDE NOTE: When use console.log in browser environment, when your object has nested levels, you might get something like
> a: [...]
After clicking > in the web console, you will see the value, but this value is determined at the moment you clicked, not the moment it ran through your console.log.
The best way to get the value at the time you logged it, use console.log(JSON.stringify(object)) instead.
To format the JSON output, passing params to stringify function like below:
console.log(
JSON.stringify
(
obj, // the object you want to log
null,
2 // the indent counts by spaces for each level
)
)
Because method toString returns it. For print object data just pass object to console.log method like console.log(list)

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.

"Can only update a mounted or mounting component" warning comes up while using a setInterval

I am developing an App with React Native. I need to retrieve some data using fetch every 30 seconds. My code works fine and it retrieves the data correctly every 30 seconds. My problem is that as soon as I redirect to another screen, I get the following warning:
Warning: Can only update a mounted or mounting component. This usually
means you called setState() on an unmounted component. This is a
no-op. Please check the code for the xxxxxxxxx component.
Here is my code:
dataGet() {
listColumnFetch(this).then(result => {
let ColumnData = result.list;
let ColumnDataArray = Object.keys(ColumnData).map((key) => { return ColumnData[key] });
console.log("serverDataArray:", this.ColumnDataArray);
this.setState({
ColumnData,
ColumnDataArray,
isLoading: false,
CurrentData: new Date(),
});
});
}
componentDidMount() {
this.dataGet();
this.interval = setInterval(() => this.dataGet(), 30000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
Although I did clearInterval in componentWillUnmount, the App gives me that warning every 30 seconds in other pages. It seems that the timer didn't stop and it is in background. Can you help me to solve this problem?
UPDATE:
Also here I try to redirect to another page. Here is the rest of my code:
onPressNew() {
this.props.navigation.navigate('RechargeElectricCar', {user: this.state.user, activeSection: 'NoChargeInProgress_2a'});
}
render() {
if (this.state.isLoading) {
return(
<View>
<ActivityIndicator size="large" color="#79b729"/>
</View>
);
}
return (
<View style={styles.container} ref="park-progress-ref">
<View style={styles.titleContainer}>
<Text style={styles.itemBold}>{I18n.t('queste_ricariche')}</Text>
</View>
<View style={ styles.rowSep } />
<View style={ styles.listContainer } >
<FlatList
ItemSeparatorComponent={ () => <View style={ styles.rowSep } /> }
horizontal={false}
data={this.state.result}
renderItem={
({item}) => (
<View style={styles.containerrow}>
<View style={styles.viewPark}>
<Text style={styles.itemBold}> {I18n.t('Data_e_ora_inizio')}: <Text style={styles.itemNormal}>{item.start}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Data_e_ora_termine')}: <Text style={styles.itemNormal}>{item.end}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Energia')}: <Text style={styles.itemNormal}>{item.energy_delivered} KWh</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Colonna')}: <Text style={styles.itemNormal}>{item.column_id}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Costo_della_ricarica')}: <Text style={styles.itemNormal}>€ {item.amount}</Text></Text>
<Text style={styles.itemBold}> {I18n.t('Aggiornamento_del')}: <Text style={styles.itemNormal}>{this.currentTime()}</Text></Text>
</View>
<View style={styles.rowCenter}>
<Button label={I18n.t('Via_questa_ricarica')} color={defStyleValues.RechargeElectricCar} onPress={ () => {console.log("MARCO log"); this.onPressTopUp(item.column_id)} } />
</View>
</View>
)
}
keyExtractor={item => item.id}
/>
</View>
<View style={ styles.rowSep } />
<View style={ styles.buttonContainer } >
<FadeInView
duration={2000}
style={{ alignItems: 'center' }}>
<ButtonIcon_White onPress={ () => { this.onPressNew() }} label={I18n.t('Nuova_ricarica')} />
</FadeInView>
</View>
</View>
);
}
When you navigate to a new screen, the new screen will be pushed on top of the previous screen. Because of this the previous screen will not be unmounted. This is why your interval is not clearing.
What you can do is to set a variable or a state value before doing a redirection and then checking the value before doing another setState.
Another thing to consider is to changing the value when you come back to previous screen. To handle that you can pass a function as a parameter to next screen and run it when the next screen's componentWillUnmount like below.
Example
onPressNew() {
// set stop value before navigating
this.setState({ stop: true }, () => {
this.props.navigation.navigate('RechargeElectricCar', {
user: this.state.user,
activeSection: 'NoChargeInProgress_2a',
onBack: this.onBack // Added this param for changing state to false
});
});
}
onBack = () => {
this.setState({stop: false});
}
//....
dataGet() {
// check stop value before fetching
if(this.state.stop !== true) {
listColumnFetch(this).then(result => {
let ColumnData = result.list;
let ColumnDataArray = Object.keys(ColumnData).map((key) => { return ColumnData[key] });
console.log("serverDataArray:", this.ColumnDataArray);
this.setState({
ColumnData,
ColumnDataArray,
isLoading: false,
CurrentData: new Date(),
});
});
}
}
On next screen (RechargeElectricCar screen)
componentWillUnmount() {
this.props.navigation.state.params.onBack()
}
Problem is with your setState setting state when your component has unmounted.
componentDidMount() {
this.isMountedComp = true
}
dataGet() {
listColumnFetch(this).then(result => {
let ColumnData = result.list;
let ColumnDataArray = Object.keys(ColumnData).map((key) => { return ColumnData[key] });
console.log("serverDataArray:", this.ColumnDataArray);
if(this.isMountedComp) {
this.setState({
ColumnData,
ColumnDataArray,
isLoading: false,
CurrentData: new Date(),
});
});
}
}
componentWillUnMount() {
clearInterval(this.interval);
this.isMountedComp = false
}
This will remove the warning errors.

Autocomplete with react native

can someone please help me debug this code, I'd like to use 'react-native-autocomplete-input' library to autocomplete a search text input, basically the api request return a list of stock symbols and company names and I'm thinking to store this file locally to make it faster, for right now I just want to make it work using fetch. The autocomplete must search in responseJson.symbol
Here's what I've done so far:
import Autocomplete from 'react-native-autocomplete-input';
import React, { Component } from 'react';
import { StyleSheet, Text, TouchableOpacity, Platform,
ScrollView, View, ActivityIndicator,
} from 'react-native';
class AutocompleteExample extends Component {
constructor(props) {
super(props);
this.state = {
symbols: [],
query: '',
};
}
searchSymbol(query) {
if (query === '') {
return [];
}
const { symbols } = this.state;
const url = `https://api.iextrading.com/1.0/ref-data/symbols`;
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
symbols:responseJson.symbol,
});
})
.catch((error) => {
console.error(error);
});
return symbols;
}
render() {
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
const { query } = this.state;
const symbols = this.searchSymbol(query);
return (
<ScrollView>
<View style={styles.MainContainer}>
<Text style={{fontSize: 12,marginBottom:10}}> Type your favorite stock</Text>
<Autocomplete
autoCapitalize={true}
containerStyle={styles.autocompleteContainer}
data={symbols}
defaultValue={query}
onChangeText={text => this.setState({ query: text })}
placeholder="Enter symbol"
renderItem={({ symbol }) => (
<TouchableOpacity onPress={() => this.setState({ query: symbol })}>
<Text style={styles.itemText}>
{symbol}
</Text>
</TouchableOpacity>
)}
/>
</View>
</ScrollView>
);
}
}
const styles = StyleSheet.create({
MainContainer :{
justifyContent: 'center',
flex:1,
paddingTop: (Platform.OS === 'ios') ? 20 : 20,
padding: 5,
},
autocompleteContainer: {
borderWidth: 1,
zIndex:999,
borderColor: '#87ceeb',
},
itemText: {
fontSize: 17,
color:'#000000',
}
});
module.exports = AutocompleteExample
I don't see any error on the console and the api request is working correctly, I just can't access symbols const Do I have to render something like cloneWithRows(responseJson.symbols) ? Thanks
First of all, the searchSymbol method should be either binded in the component constructor, or declared as a class property. Otherwise, "this" will not point to the component instance.
Then, it seems that your state does not have an isLoading property, but you use it in your render function.
What you should probably do is call your asynchronous searchSymbol method in the componentDidMount lifecycle method. When the promise of the fetch resolves, you should put the result in the state as well as put the isLoading boolean to false. Then your component will re-render with the now available data.

Resources