React-Redux FlatList Updation - reactjs

I have an AuditionsList component which displays a FlatList. In a different reducer I'm changing Settings called location and roleType. Depending on these new settings I want to refresh the AuditionList.
Here is my code:
import React from 'react';
import { Text, View, FlatList, ActivityIndicator } from 'react-native';
import { connect } from 'react-redux';
import AuditionItem from './AuditionItem';
import Auditions from './../data/Auditions';
class AuditionsList extends React.Component {
constructor(props) {
super(props);
this.state = { isLoading: true, data: [], refresh: false }
}
componentDidMount() {
this._refreshData();
}
_onRefresh() {
this.setState({ isLoading: true }, this._refreshData() );
}
_refreshData = () => {
Auditions.fetchAuditions(this.props.productionType, this.props.location, this.props.roleType).then(auditions => {
this.setState({ isLoading: false, data: this._addKeysToAuditions(auditions) });
});
}
_addKeysToAuditions = auditions => {
return auditions.map(audition => {
return Object.assign(audition, { key: audition.Role});
});
}
_renderItem = ({ item }) => {
return (
<AuditionItem
auditionId={item.objectId}
role={item.Role}
project={item.Project.Name}
productionType={item.Project.ProductionType.Type}
auditionDate={JSON.stringify(item.Date.iso)}
productionHouse={item.Project.ProductionHouse.Name}
/>
);
}
render() {
console.log("Here...");
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<FlatList onRefresh={() => this._onRefresh()} refreshing={this.state.isLoading} data={this.state.data} renderItem={this._renderItem} />
</View>
);
}
}
const mapStateToProps = state => {
return {
location: state.settings.location,
roleType: state.settings.roleType,
};
}
export default connect(mapStateToProps)(AuditionsList);
I need to call the _onRefresh() or _refreshData() function AFTER the callback to mapStateToProps (which runs successfully) but BEFORE the FlatList re-renders (which also happens successfully, but with old data). So where do I call the _onRefresh() or _refreshData() functions? Putting them in render() causes an infinite loop.

Have you tried using the ComponentDidUpdate react lifecycle method? This will fire after you receive new props, and you could make a call to this._refreshData there.
https://reactjs.org/docs/react-component.html#componentdidupdate

Related

React Native is Not fetching the latest data from API call

I sicerely Apologies if this has been asked before. I am a bit new to react native and react in general.
my react nativee code is not fetcing the latest data
see the code for the list component below
I would deeply appreciate it if you can tell me why this is happening and how to make it pick the latest data
import React, { Component } from "react";
import {
FlatList,
Text,
View,
StyleSheet,
ScrollView,
ActivityIndicator
} from "react-native";
import Constants from "expo-constants";
import { createStackNavigator } from "#react-navigation/stack";
import { toCommaAmount } from "./utilities";
const Stack = createStackNavigator();
function MyStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={ExpenseList} />
<Stack.Screen name="NewTransaction" component={ExpenseForm} />
</Stack.Navigator>
);
}
function Item({ title }) {
return (
<View style={styles.item}>
<Text style={styles.title}>{title}</Text>
</View>
);
}
class ExpenseList extends Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
dataSource: null
};
}
componentDidMount() {
return fetch("https://example.com/expense/api/get_all.php")
.then(response => response.json())
.then(responseJson => {
this.setState({
isLoading: false,
dataSource: responseJson.items
});
})
.catch(error => console.log(error));
}
render() {
if (this.state.isLoading) {
return (
<View style={styles.container}>
<ActivityIndicator />
</View>
);
} else {
let myExpenses = this.state.dataSource.map((val, key) => {
return (
<View key={key} style={styles.item}>
<Text>
{val.title} {toCommaAmount(val.amount)}
</Text>
<Text>{val.date_time}</Text>
</View>
);
});
return <View style={styles.container}>{myExpenses}</View>;
}
}
}
export default ExpenseList;
ComponentDidMount is a void function. I assume you have this problem because you try to return the result of the fetch execution.
Can you remove your api call out of componentDidMount(), because it gets invoked only once. Rename it to getRealData() and attach it to a button click. So every click on button will being up latest data from backend.

Getting "Can't perform a React state update on an unmounted component" when switching between screens

When switching between the Home.js and Chat.js files, I get this warning: "Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method". I removed the only listener that's on Chat.js and tried only setting state when the component is mounted in Home.js and removing it on unmount but I still get this warning.
Home.js
import React, { Component } from "react";
import { View, FlatList } from "react-native";
import { ListItem } from "react-native-elements";
import fireStoreDB from "../database/FirestoreDB";
let _isMounted = false;
export default class Home extends Component {
constructor(props) {
super(props);
this.state = {
usersInfo: [],
refreshing: false
};
}
componentDidMount() {
_isMounted = true;
this.LoadUsers();
}
componentWillUnmount() {
_isMounted = false;
}
LoadUsers = () => {
fireStoreDB
.getAllUsersExceptCurrent()
.then(users =>
Promise.all(
users.map(({ id, username, avatar }) =>
fireStoreDB
.getUserLastMessage(fireStoreDB.getUID, id)
.then(message => ({ id, username, avatar, message }))
)
)
)
.then(users => {
if (_isMounted) {
this.setState({
usersInfo: users.filter(x => typeof x.avatar !== "undefined"),
refreshing: false
});
}
});
};
renderItem = ({ item }) => (
<ListItem
onPress={() => {
this.props.navigation.navigate("Chat", {
userTo: item.id,
UserToUsername: item.username,
LoadUsers: this.LoadUsers
});
}}
title={item.username}
subtitle={item.message}
leftAvatar={{ source: { uri: item.avatar } }}
bottomDivider
chevron
/>
);
render() {
return (
<View>
<FlatList
data={this.state.usersInfo}
renderItem={this.renderItem}
keyExtractor={item => item.id}
refreshing={this.state.refreshing}
onRefresh={() => {
this.setState({ refreshing: true });
this.LoadUsers();
}}
/>
</View>
);
}
}
Chat.js
import React, { Component } from "react";
import { View, KeyboardAvoidingView } from "react-native";
import { HeaderBackButton } from "react-navigation-stack";
import { GiftedChat } from "react-native-gifted-chat";
import * as Progress from "react-native-progress";
import fireStoreDB from "../database/FirestoreDB";
const Themes = {
primaryTheme: "#30D921",
secondaryTheme: "#B32D83",
layoutTheme: "#c0c0c0"
};
export default class Chat extends Component {
static navigationOptions = ({ navigation }) => ({
title: navigation.getParam("UserToUsername"),
headerLeft: (
<HeaderBackButton
onPress={() => {
navigation.state.params.LoadUsers();
navigation.goBack();
}}
/>
)
});
constructor(props) {
super(props);
this.state = {
messages: [],
userToId: this.props.navigation.getParam("userTo")
};
}
componentDidMount() {
fireStoreDB.getMessages(
message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message)
})),
this.chatId
);
}
componentWillUnmount() {
fireStoreDB.removeSnapshotListener(this.chatId);
}
// gifted chat user props
get user() {
return {
_id: fireStoreDB.getUID,
name: fireStoreDB.getName,
avatar: fireStoreDB.getAvatar
};
}
// merge ids between two parties for one to one chat
get chatId() {
const userFromId = fireStoreDB.getUID;
const chatIdArray = [];
chatIdArray.push(userFromId);
chatIdArray.push(this.state.userToId);
chatIdArray.sort(); // prevents other party from recreating key
return chatIdArray.join("_");
}
render() {
if (this.state.messages.length === 0) {
return (
<View
style={{
alignItems: "center",
marginTop: 260
}}
>
<Progress.Bar indeterminate color={Themes.primaryTheme} />
</View>
);
}
return (
<View style={{ flex: 1 }}>
<GiftedChat
messages={this.state.messages}
onSend={messages => fireStoreDB.sendMessages(messages, this.chatId)}
user={this.user}
/>
<KeyboardAvoidingView behavior="padding" keyboardVerticalOffset={80} />
</View>
);
}
}
FirestoreDB.js
removeSnapshotListener = chatId => {
firebase
.firestore()
.collection("messages")
.doc(chatId)
.collection("chats")
.orderBy("createdAt")
.onSnapshot(() => {});
};
UPDATE:
With your implementation, you cannot unsubribe messages collection.
You could try to return unsubscribe function from getMessages, then use it in componentWillUnmount
FirestoreDB.js
getMessages = (callback, chatId) => {
return firebase
.firestore()
.collection("messages")
.doc(chatId)
.collection("chats")
.orderBy("createdAt")
.onSnapshot(callback);
}
Chat.js
componentDidMount() {
this.unsubcribe = fireStoreDB.getMessages(
message =>
this.setState(previousState => ({
messages: GiftedChat.append(previousState.messages, message)
})),
this.chatId
);
}
componentWillUnmount() {
this.unsubcribe();
}

React Native Search filter not working properly

I am trying to implement react native search filter. I have my data array on the parent screen and I applied the search filter on the parent file and passed the filtered data array as props to the child screen. But no filter is happening. I am confused or no clue what wrong i am doing. I have the following codes:
ParentScreen.js
import SearchInput, { createFilter } from 'react-native-search-filter';
...
const KEYS_TO_FILTERS = ['title']
export default class ProductScreen extends Component {
constructor(props) {
super(props)
this.state = {
items: API,
searchTerm: '',
}
}
searchUpdated(term) {
this.setState({ searchTerm: term })
}
render() {
const filteredItems = this.state.items.filter(createFilter(this.state.searchTerm, KEYS_TO_FILTERS))
let cardGridWithBodyProps = {
navigation: this.props.navigation,
api: filteredItems,
gridNumber: 2,
}
return (
<Container>
<ScrollView showsVerticalScrollIndicator={false}>
<View>
<Header>
<SearchInput
onChangeText={(term) => { this.searchUpdated(term) }}
placeholder="Search"
/>
</Header>
</View>
<View>
<CardGridWithBody {...cardGridWithBodyProps} />
</View>
</ScrollView>
</Container>
)
}
}
ChildScreen.js
export default class CardGridWithBody extends Component {
constructor(props) {
super(props)
this.state = {
items: this.props.api
}
}
renderGrid(gridArray) {
return gridArray.map((row, rowIndex) => (
<Row key={rowIndex}>
{row.map((col, colIndex) => (this.renderColumn(col, rowIndex,colIndex)))}
</Row>
));
}
renderColumn(colItem, rowIndex, colIndex) {
return (
<Col key={colIndex}>
<Text>{colItem.title}</Text>
</Col>
)
}
renderContent() {
let gridArray = this.state.items
return this.renderGrid(gridArray)
}
render() {
return (
this.renderContent()
)
}
}
Instead of saving the data in state, access it directly from props. If you save it in state, you'll need to update it manually using lifecycle methods such as shouldComponentUpdate or getDerivedStateFromProps
renderContent = () => {
let gridArray = this.props.api;
return this.renderGrid(gridArray);
}
In parent screen convert
searchUpdated(term) {
this.setState({ searchTerm: term })
}
to
searchUpdated = term => {
this.setState({ searchTerm: term })
}
and in your child component you can do
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.api !== prevState.api) {
return { api: nextProps.api };
}
return null;
}
componentDidUpdate(prevProps, prevState) {
if (
this.state.items !==
prevState.items
) {
this.setState({ items: api });
}
}

React Component Props are receiving late. (Meteor JS)

I am working on a react-native and meteor js project.
My problem is that the props received from withTracker() function are only received in componentDidUpdate(prevProps) I don't get them in constructor or componentWillMount.
Another issue is when i pass props directly from parent to child. it receives them late due to which my component does not update
iconGroups prop comes from withTracker() method
and openSection props which i am using in this showGroupIcons()
is passed directly from parent to this component.
I want to open Accordian section that is passed to it via parent. but problem is in componentDidUpdate(prevProps) I am changing state due to which component re-renders.
openSection variable by default value is Zero. when props arrvies it value changes which i required But Accordian does not update.
Below is my code
import React, { Component } from 'react';
import Meteor, { withTracker } from 'react-native-meteor';
import {
View, Image, ScrollView, TouchableOpacity,
} from 'react-native';
import PopupDialog from 'react-native-popup-dialog';
import {Text, Icon, Input, Item, List,} from 'native-base';
import Accordion from 'react-native-collapsible/Accordion';
import { Col, Row, Grid } from 'react-native-easy-grid';
import styles from './styles';
import CONFIG from '../../config/constant';
import {MO} from "../../index";
const staticUrl = '../../assets/img/icons/';
class IconPickerComponent extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: [],
itemName: 'apple1',
activeSections: 0,
showAccordian: true,
accordianData: []
};
}
componentDidUpdate(prevProps) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
componentDidMount() {
this.props.onRef(this);
}
componentWillUnmount() {
this.props.onRef(null);
}
method() {
// this.setState(...this.state,{
// searchText: ''
// })
this.iconPicker.show(); // show icon picker
}
onSearchChange(text) {
this.setState({
showAccordian: !(text.length > 0)
});
const searchText = text.toLowerCase();
const filteredItems = this.state.dataSource.filter((item) => {
const itemText = item.name.toLowerCase();
return itemText.indexOf(searchText) !== -1;
});
this.setState({ filteredItems });
}
onIconSelect(item) {
this.setState({
itemName: item,
});
this.iconPicker.dismiss();
if (this.props.onIconChanged) {
this.props.onIconChanged(item);
}
}
_renderSectionTitle = section => {
return (
<View style={styles.content}>
<Text></Text>
</View>
);
};
_renderHeader = section => {
return (
<View style={styles.accordHeader}>
<Text style={{color: 'white'}}>{this.state.showAccordian} - {section.group}</Text>
<Text>
<Icon style={styles.downArrow} name="ios-arrow-down" />
</Text>
</View>
);
};
_renderContent = section => {
return (
<View style={styles.accordContent}>
{
section.images.map((img, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(img)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: CONFIG.ICON_URL+ img + '.png'}}/>
</View>
</TouchableOpacity>
))
}
</View>
);
};
_updateSections = activeSections => {
this.setState({ activeSections });
};
hasGroupIcons() {
return this.props.iconGroups.length > 0;
};
showGroupIcons() {
if(this.state.showAccordian){
let openSection;
if(!!this.props.openSection) {
let groupIndex = this.state.accordianData.findIndex(icon => icon.group === this.props.openSection);
if(groupIndex !== -1) {
openSection = groupIndex;
} else {
openSection = 0;
}
} else {
openSection = 0;
}
return(<Accordion
sections={this.state.accordianData}
activeSections={this.state.activeSections}
renderSectionTitle={this._renderSectionTitle}
renderHeader={this._renderHeader}
renderContent={this._renderContent}
onChange={this._updateSections}
initiallyActiveSection={openSection} />);
} else {
return(<View style={{flexWrap: 'wrap', flexDirection: 'row'}}>
{
this.state.filteredItems.map((item, key) => (
<TouchableOpacity onPress={() => this.onIconSelect(item.name)} key={key}>
<View style={styles.iconsGrid}>
<Image style={styles.image} source={{uri: item.icon}}/>
</View>
</TouchableOpacity>
))
}
</View>)
}
};
render() {
return (
<PopupDialog
overlayOpacity={0.8}
overlayBackgroundColor="#414141"
dialogStyle={styles.dialogBox}
containerStyle={styles.dialogContainer}
ref={(popupDialog) => { this.iconPicker = popupDialog; }}
>
<ScrollView>
<View style={styles.dialogInner}>
<Item searchBar rounded style={styles.searchbar}>
<Icon style={styles.searchIcon} name="search" />
<Input onChangeText={this.onSearchChange.bind(this)} style={styles.inputSearch} placeholder="Search" />
</Item>
{
this.hasGroupIcons() && this.showGroupIcons()
}
</View>
</ScrollView>
</PopupDialog>
);
}
}
export default withTracker(params => {
MO.subscribe('ipSubsId3', 'IconGroups');
return {
iconGroups: MO.collection('IconGroups', 'ipSubsId3').find({}),
};
})(IconPickerComponent);
I am new to react. I am assuming when props change component re-renders.
Use this life cycle method
static getDerivedStateFromProps(prevProps, prevState) {
if(prevProps.iconGroups !== this.props.iconGroups) {
let images = this.props.iconGroups.map(icon => icon.images);
let flatten = [].concat.apply([], images).map(img => { return {name: img, icon: CONFIG.ICON_URL+img+'.png'} })
this.setState({ filteredItems: flatten, dataSource: flatten, accordianData: this.props.iconGroups });
}
}
getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates. It should return an object to update the state, or null to update nothing.
Read more about this lifecycle method here
I have fixed this issue. Actually my concepts were not right. I thought props are first received in constructor and componentWillMount. But I get all props in render() and everything works fine i dont have to use any lifecycle method to use props now

React Native - Adding items to FlatList using Redux

I'm trying to implement a simple ToDo list exercise app with Redux.
I have a TextInput, a Button and a FlatList, the items are stored as an array in the store and handled by a reducer.
When the button is pressed an item with the text from the input should be
added to the list.
Currently when I press the button nothing happens, the list does not seem to render and new elements are not added.
I suspect something could be wrong in the onPress handling but I'm not sure, I could have made mistakes elsewhere, I'm pretty new to React Native
Any help for figuring this out is welcome.
This is my code so far (without the imports):
action
export const addItem = item =>({type: ADD_ITEM, payload: item});
reducer
const initialState = {
items: [],
};
const itemReducer = (state = initialState, action) => {
switch(action.type) {
case ADD_ITEM:
return { ...state, items: [...state.items, action.payload] };
default:
return state;
}
};
export default itemReducer;
store
const store = createStore(itemReducer);
export default store;
input form
const mapDispatchToProps = dispatch => {
return {
addItem: item => dispatch(addItem(item))
};
};
class ItemInput extends Component {
constructor(props) {
super(props);
this.state = { itemText: 'Add an item' };
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit() {
//event.preventDefault();
const { itemText } = this.state;
this.props.addItem( { itemText } );
this.setState({ itemText: "" });
}
render() {
return (
<View>
<TextInput
style={{height: 40, borderColor: 'gray', borderWidth: 1}}
onChangeText={(itemText) => this.setState({itemText})}
value={this.state.itemText}
/>
<Button
onPress={this.handleSubmit}
title="Submit"
color="#841584"
/>
</View>
);
}
}
export default ItemInput = connect(null, mapDispatchToProps)(ItemInput);
list
const mapStateToProps = state => {
return { items: state.items };
};
class ItemList extends Component {
constructor(props) {
super(props);
this.state = {
};
this.renderItem = this.renderItem.bind(this);
this.keyExtractor = this.keyExtractor.bind(this);
}
render() {
return (
<View style={{flex:1, backgroundColor: '#F5F5F5', paddingTop:20}}>
<FlatList
ref='listRef'
data={this.props.items}
renderItem={this.renderItem}
keyExtractor={this.keyExtractor}/>
</View>
);
}
keyExtractor = (item, index) => index.toString();
renderItem({item, index}) {
return (
<View style={{backgroundColor: 'white'}}>
<Text>{item.title}</Text>
</View>
)
}
}
export default List = connect(mapStateToProps)(ItemList);
todo list component
class ToDoList extends Component {
render() {
return (
<View>
<ItemInput/>
<List/>
</View>
);
}
}
export default ToDoList;
App (root)
export default class App extends Component {
render() {
return (
<Provider store={store}>
<ToDoList/>
</Provider>
);
}
}

Resources