Flat List not re-rendering even when I change a state - react-native

Expo-cli: 2.2.0
React-Navigation: 2.18.0
I have the following two screens for React Navigation, where one is to input the form details and another is the screen where the user can either edit on the submissions or confirm.
My Input interface looks like this:
export default class PickDropInterface extends
React.Component<NavigationProps<>> {
this.state = { tasks: [],
}
onSubmit = () => { this.props.navigation.navigate("Confirmation",
{tasks: this.state.tasks, deleteItem: this.deleteItem.bind(this)}); }
deleteItem = (key) => { var filteredTasks =
this.state.tasks.filter(function (item) { return (item.key !==key);
});
render() {
return (
<ItemDetail onSubmit={this.onSubmit} /> ) }
My Confirmation screen looks like this:
export default class Confirmation extends React.Component<NavigationProps<>> {
this.state={
refresh: false,
}
deleteItem = (key) => {
this.props.navigation.state.params.deleteItem(key);
this.setState({
refresh: !this.state.refresh
})
}
_renderItem =({ item }) =>
(
<View style={styles.cardStyle}>
<Button
primary
label="Delete" onPress= {() => {this.deleteItem(item.key)}} /></View>
)
render() {
return (
<FlatList data={task}
renderItem= {this._renderItem}
keyExtractor= {(item) => item.key.toString()}
extraData={this.state} />
)
}
Expected Output:
The delete button to prompt refresh in the FlatList and show the new Task list.
Current Output:
https://www.youtube.com/watch?v=RmrurTBQpak&feature=youtu.be

I don't know why FlatList didn't re-render, but I found a much simpler solution to what I wanted.
I used conditional rendering instead and I kind of think it's the way to do it instead of navigating to the other screen.
What I did is:
I made a new state called 'orderComplete' and set it to false as default.
Whenever, it is 'false', I made inputInterface above to render whereas it was 'true', I made the above ConfirmationScreen render.
More on Conditional Rendering can be found in React's official documentation.
FlatList above works like a charm now.

Related

loading on navigating between screens

I'm new in RN. When I want to navigate between screens I create this function:
displayScreen2 = () => {
this.props.navigation.navigate("screen2")
}
and I call it in onPress={this.displayScreen2}
with TouchableOpacity or any Touchable when the user clicks he has to wait 1 second or 2 before displaying the screen. So what I want is to change the Touchable icon to an loader.
It's simple if I use a conditional rendering but I don't know how to do it now, when I have to change my state? Any suggestions?
this is my approach:
<TouchableOpacity
style={Styles.topButton}
onPress= {() => {
this.setState({loading: 'load'},
() => {
displayScoreListView()
// this.setState({loading: 'icone'})
}
)
}}
>
<Text style={Styles.scoreListButtonTextRed}>{this.state.loading}</Text>
that not work, tha state change but visualy not because if I return to the first screen I have 'load' in the text component
You could create a custom component wrapping whatever Touchable you prefer, I've used this technique in my production apps before. The button has it's own state which allows you to automatically display a loading indicator when necessary.
export class ButtonWorker extends Component {
state = {
working: false
}
onButtonPress = () => {
this.setState(
{ working: true },
() => {
this.props.onPress(this.onWorkFinished);
}
);
}
onWorkFinished = () => {
this.setState({ working: false });
}
render() {
return (
<TouchableOpacity>
{this.state.working ? (
<ActivityIndicator />
) : (
this.props.children
)}
</TouchableOpacity>
);
}
}
And then use it like a normal button with additional logic!
export class NavigationScreen extends Component {
navigate = (done) => {
// ... Asynchronous logic goes here
done();
this.props.navigation.navigate("Screen2");
}
render() {
return (
<Fragment>
{/* ... */}
<ButtonWorker onPress={this.navigate} />
</Frament>
);
}
}

Warning when switching to another screen if the user has authorization

When I open the application and if the isAuth variable is true, it means the user has authorization, then I want to go to the provider screen.
My authorization component.
export const LoginScreen: NavigationStackScreenComponent<NavigationParams> = observer(({ navigation }: NavigationParams) => {
useEffect(() => {
AuthState.checkAuthentication();
}, []);
if (AuthState.isAuth) {
navigation.navigate('Provider');
}
return <View style={styles.body}>{LoaderState.loading ? <LoaderComponent /> : <AuthComponent />}</View>;
});
My provider component.
export const ProvidersScreen: NavigationStackScreenComponent<NavigationParams> = observer(({ navigation }: NavigationParams) => {
useEffect(() => {
ProvidersState.setProviders();
}, []);
return (
<View style={styles.body}>
{LoaderState.loading ? (
<LoaderComponent />
) : (
<ItemListComponent itemsList={ProvidersState.providersList} />
)}
</View>
);
});
But I get a warning. I understand that it is associated with the react-navigation library.
How to switch to another screen using a conditional statement?
Functional components are basically the equivalent to the render() function in class components. You are calling navigate before the component gets render and that's a problem.
You can either call the navigate inside your useEffect or put the conditional logic in the parent view.
In the first case, navigate will be called AFTER the component renders
useEffect(() => {
AuthState.checkAuthentication();
if (AuthState.isAuth) {
navigation.navigate('Provider');
}
}, []);
so it will be visible for half a second and then change. If you'd like to avoid this, then go for the second option.
You can put the condition in the parent. Something like
<View>{AuthState.isAuth ? <ProvidersScreen /> : <LoginScreen />}</View>

React Native: Cant rerender Image after state change

I am starting with react-native.
I am requesting a gif from the giphy API and then updating my giphyUrl in state (the state is changed) but the gif doesnt change ( the component is not rerendered).
class QuoteList extends Component {
state = { quotes: [],
giphyUrl: 'https://media.giphy.com/media/nZQIwSpCXFweQ/giphy.gif'
};
componentWillMount() {
console.log('Again?')
axios.get('https://api.tronalddump.io/search/quote?query='+this.props.characterName)
.then(response => this.setState({ quotes: response.data._embedded.quotes }))
this.getGiphy()
}
getGiphy() {
console.log('getgif')
const GiphyUrl = "https://api.giphy.com/v1/gifs/search?api_key=tutu&limit=1&q=" + this.props.characterName.replace(" ", "+");
console.log(GiphyUrl)
axios.get(GiphyUrl)
.then(response => {
console.log(response)
console.log(response.data.data[0].url)
this.setState({ giphyUrl: response.data.data[0].url })
console.log(this.state)
})
}
renderQuotes() {
return this.state.quotes.map(
quote => <QuoteDetail key={quote.quote_id} quote={quote}/>
);
}
render() {
return (
<ScrollView>
<Image
source={{uri: this.state.giphyUrl}}
style={styles.gifStyle}
/>
{this.renderQuotes()}
</ScrollView>
);
}
}
Why is the component not rerendering ? when I console.log the state in the callback of the axios request, I can see that the state is changed. Even when I try to "force" rerender (forceUpdate), it wouldnt rerender.
Try updating the key property of the image:
<Image
source={{uri: this.state.giphyUrl}}
key={this.state.giphyUrl}
style={styles.gifStyle}
/>
Adding the key prop to any view will cause a rerender of the view as long as the key is changing.

How to update the FlatList immediately after state changed?

I am working with react native.
I have component listing by using
And, when the state to give data to update the list change. It won't update immediately. It take few seconds to re-render.
so, how can I update the component immeidately
//Listcomponent
const ListGlossary = ({glossaries, onPressGlossary, navigation, searchField}) => {
return (
<FlatList
data={glossaries}
keyExtractor={(item) => item.key}
renderItem={({item}) =>
<TouchableHighlight
onPress = {() => navigation.navigate('DetailGlossaryScreen', { searchField: searchField, word: item.word, translate: item.translate})}>
<ListItem
key={`${item.key}`}
title={`${item.word}`}
/>
</TouchableHighlight>
}
/>
}
//And you can find here the home screen component
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
glossaries: [],
searchField: '',
}
}
componentDidMount () {
Promise.resolve().then(() => {this.setState({glossaries: JSONDataFromFile, isLoading: false})})
}
onSearchChange = (inputText) => {
this.setState({searchField: inputText});
}
render(){
return(
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
glossaries = this.state.glossaries;
for(let i = 0, l = glossaries.length; i < l; ++i) {
if(glossaries[i].word.toLowerCase().indexOf(searchField) === 0){
filteredWords.push(glossaries[i]);
}
}
}
{this.state.isLoading ?
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
:
<ListGlossary
navigation = {this.props.navigation}
glossaries = {filteredWords}
onPressGlossary={this.onPressGlossary}
searchField = {this.state.searchField}
/>
}
)
}
Please show the whole component, and give the length of the list.
--- Edit
I suspect you're doing too much work in the render function. You're filtering every time it gets called, and since you're passing in the navigation prop (I assume you're using React-Navigation), it'll get called frequently. If you're using a stack navigator, all the other screens are also getting re-rendered every time you navigate to a new screen. Avoid passing navigation as much as possible, or use a HOC composition to ignore it.
You probably don't need to be filtering glossaries every time the user changes the search value. Use the shouldComponentUpdate lifecycle method.

React Native implement JSON data on ListView

I'm having a problem with implementing API data in ListView. I fetched JSON using Axios.
export function fetchRateService() {
return function(dispatch) {
axios.get(RATE_URL)
.then(response => {
dispatch({
type: FETCH_RATE_SERVICE,
payload: response.data
});
})
.catch((error) => {
console.log(error);
})
}
}
Reducer. I added rates data into array
import {
FETCH_RATE_SERVICE
} from '../actions/types';
const INITIAL_STATE = {
base: '',
date: '',
rates: []
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_RATE_SERVICE:
return {
...state,
base: action.payload.base,
date: action.payload.date,
rates: [ ...state.rates, action.payload.rates ]
};
default:
return state;
}
};
This is the component
class ConturyList extends Component {
componentWillMount() {
this.props.fetchRateService();
this.createDataSource(this.props);
}
createDataSource({rates}) {
const ds = new ListView.DataSource({
rowHasChanged: (r1, r2) => r1 !== r2
});
this.dataSource = ds.cloneWithRows(rates);
}
renderRow(rate) {
return <ListItem rate={rate} />
};
render() {
console.log(this.props);
const { CardSectionStyle, textStyle, containerStyle } = styles;
const { visible, closeModal } = this.props;
return (
<Modal
visible={visible}
transparent={false}
animationType="slide"
onRequestClose={() => {this.props.closeModal()}}
>
<ListView
enableEmptySections
dataSource={this.dataSource}
renderRow={this.renderRow}
/>
</Modal>
);
}
}
const mapStateToProps = state => {
return {
rates: state.rateService.rates,
date: state.rateService.date,
base: state.rateService.base
};
}
export default connect(mapStateToProps, { fetchRateService } )(ConturyList);
The problem is I can see the props data using console.log(this.props);
enter image description here
I'm spending more than 3 days to figure out why this is not working. I tried using map() adding on
renderRow(rate) {
return rate.map((data) => {
return <ListItem rate={data} />
};
but it did not work. All the conutry code is in one object, Do I need to split the data by commas?.
Appreciate you helps. Thank you
UPDATE
So I'm trying to implement FlatList instead using ListView. The problem is on the JSON data. enter image description here. I want to implement key which are CountryCurrencyCode(AUD, JPN, etc..) to FlatList. Since rates is an object within an object, I added rates object into an array(reducer). But this.props.rates[0] can't be implemented on data property of FlatList. What kind of method can I try? I can't think of anything. I could print out key using map() when rates is object and then I can't implement it on the FlatList.
I would recommend switching over to the new FlatList component over ListView. FlatList just accepts an array of data to hydrate.
Initiate this.state.datasource as an empty array
constructor(props) {
super(props);
this.state = {
dataSource: [],
}
}
Fetch your data and hydrate this.state.dataSource from your Redux reducer/action
ComponentDidMount(){
this.props.fetchRateService();
var myData = this.props.rates[0];
this.setState({
dataSource:myData
)}
}
Now that your this.state.dataSource is set, we can populate FlatList
<FlatList
data={this.state.dataSource}
renderItem={({item})=>this.renderRow(item)}
/>
Flat List will throw a warning about a key extractor
Add this line below to the FlatList component. You will need to change 'item.key' to fit your own unique child. You can just keep it out for now for development.
keyExtractor={item => item.key}
You should see your data now! Keep in mind, you don't have to set the this.state.dataSource. Its just how I do it. You can plug 'this.props.rates' array directly into FlatList instead. Check out the FlatList docs for all the different things you can do with it. Hope this helps!