I am trying to fetch data through API. The data is returned in array, i parsed it to JSON and it shows perfectly in Console Log. But i can't show it on screen, the console shows following warning,
Warning: Encountered two children with the same key, 221. Keys should be unique so that components maintain
their identity across updates. Non-unique keys may cause children to be duplicated and/or omitted — the behavior is unsupported and could change in a future version.
This is my code:
import React from 'react';
import { Container, Header, Title, Drawer, Content, Button, Left, Right, Body, Text} from 'native-base';
import { Alert, View, TouchableOpacity, SafeAreaView } from 'react-native';
import { MaterialIcons } from '#expo/vector-icons';
import { Ionicons } from '#expo/vector-icons';
import SideBar from './components/SideBar';
import { FlatList } from 'react-native-gesture-handler';
export default class QrScan extends React.Component{
constructor(props) {
super(props)
this.state = {
resourcedata:'',
};
this.resourceAllocationList = this.resourceAllocationList.bind(this);
}
closeDrawer = () => {
this.drawer._root.close();
}
openDrawer = () => {
this.drawer._root.open();
}
resourceAllocationList() {
fetch('https://api.idepoz.com/ncl/api/getResource', {
method: 'GET',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
}).then((response) => response.json())
.then((responseJson) => {
if(responseJson)
{
var jsonData = JSON.stringify(responseJson.data);
var resultdata = JSON.parse(jsonData);
//console.log(resultdata);
this.setState({resourcedata:resultdata});
}
}).catch((error) => {
console.error(error);
});
}
render()
{
const getHeader = () => {
return <Text>{'Resource Allocation'}</Text>;
};
const getFooter = () => {
if (this.state.loading) {
return null;
}
return <Text>{'Loading...'}</Text>;
};
return(
<Drawer
ref={(ref) => { this.drawer = ref; }}
content={<SideBar navigator={this.navigator} closeDrawer={this.closeDrawer} usertoken=
{this.props.navigation.state.params.usertoken} />}
onClose={() => this.closeDrawer()} >
<Container>
<Header>
<Left>
<Button transparent onPress={this.openDrawer.bind(this)}>
<MaterialIcons name="list" size={40} color="#FFFFFF" />
</Button>
</Left>
<Body>
</Body>
<Right>
<Button transparent>
<Ionicons name="search" size={40} color="#FFFFFF" onPress={() =>
Alert.alert('Search Button pressed')} />
</Button>
</Right>
</Header>
<Content>
<SafeAreaView style={{
flexDirection:"row",
justifyContent:'center',
marginTop: 20,
alignItems: 'center',
marginHorizontal: 20,
}}>
<TouchableOpacity onPress={this.resourceAllocationList}>
<Text>Press Here</Text>
<FlatList data={this.state.resourcedata}
renderItem={({ item }) => {
<Text>{ item.id }</Text>
}}
ListHeaderComponent={getHeader}
ListFooterComponent={getFooter}/>
</TouchableOpacity>
</SafeAreaView>
</Content>
</Container>
</Drawer>
);
}
}
Return Data in Console shows like below:
Array [
Object {
"allocation_space": 1,
"created_at": "2021-03-26 15:49:55",
"created_by": 1,
"date": "2021-04-19",
"deleted_at": null,
"deleted_by": null,
"duration": "01:00:00",
"employee_id": 2,
"end_time": "01:05:00",
"id": 73,
"is_active": 1,
"is_payable": 1,
"order_plan_id": 1,
"price": 13,
"resources_allocation_id": 73,
"serviceuser_id": 1,
"start_time": "00:05:00",
"status": "Approved",
"updated_at": "2021-04-19 07:56:08",
"updated_by": 1,
}.........
Can anyone help how to return above data on screen ?
Try adding extraData prop to Flatlist.
"By passing extraData={selected} to FlatList we make sure FlatList itself will re-render when the state changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is a PureComponent and the prop comparison will not show any changes."
https://docs.expo.dev/versions/latest/react-native/flatlist/
Also adding "key" prop to your Flatlist Text element will take away the error if you don't have multiple same ids in your data. In that case you could use index as key, but this is not the best practice.
So changing your flatlist to...
<FlatList
data={this.state.resourcedata}
extraData={this.state.resourcedata}
renderItem={({ item }) => {
<Text key={item.id}>{ item.id }</Text>
}}
ListHeaderComponent={getHeader}
ListFooterComponent={getFooter
/>
Also I have used props like: initialNumToRender={8} windowSize={16} to reduce memory consumption but this depends on your use case and list size
More information about Flatlist props: https://docs.expo.dev/versions/latest/react-native/flatlist/
(This is my first answer to question so be merciful to me)
EDIT
If braces {} are used in renderItem={} there needs to be return ()
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => {
return (
<Text key={index }>{ item.id }</Text>
);
}}
Otherwise you could use normal braces () in renderItem which would look like...
keyExtractor={(item, index) => index.toString()}
renderItem={({ item, index }) => (
<Text key={index}>{ item.id }</Text>
)
}
I usually use {} and return as then I can even put logic before return () statement like console.log()
Related
I call myGET_REQUESTaction in a screen, my Sagamakes the request and triggers my GET_SUCCESS action but, my screen doesn't re-render on it's own, only when I update the component's state, a re-render is triggered and the new props reflecting the store show up.
so... my store apparently works fine, in Reactotron the store is filled, as you can see below:
https://user-images.githubusercontent.com/27635248/54751638-52671b80-4bba-11e9-9f9b-b7eee7f3deba.png
My user Reducer:
export const Types = {
GET_SUCCESS: 'user/GET_SUCCESS',
GET_REQUEST: 'user/GET_REQUEST',
GET_FAILURE: 'user/GET_FAILURE',
UPDATE_USER: 'user/UPDATE_USER',
REHYDRATE_SUCCESS: 'user/REHYDRATE_SUCCESS',
REHYDRATE_FAILURE: 'user/REHYDRATE_FAILURE',
}
const INITIAL_STATE = {
data: {},
status: {
loading: false,
error: null,
}
}
export default function user(state = INITIAL_STATE, action) {
switch (action.type) {
case Types.GET_SUCCESS: {
return {
...state,
data: action.payload.user,
status: {
...state.status,
loading: false,
error: null
}
}
}
case Types.GET_REQUEST: {
return {
...state,
status: {
error: null,
loading: true
}
}
}
case Types.GET_FAILURE: {
if (Object.keys(state.data).length) {
action.payload.isStateEmpty = false
return {
...state,
status: {
...state.status,
loading: false
}
}
} else {
return {
...state,
status: {
...state.status,
loading: false
}
}
}
}
case Types.REHYDRATE_SUCCESS: {
return {
...state,
data: action.payload.user,
status: {
error: null,
loading: false
}
}
}
case Types.REHYDRATE_FAILURE: {
return {
...state,
status: {
loading: false,
error: action.payload.error
}
}
}
case Types.UPDATE_USER: {
return {
...state,
data: {
...state.data,
...action.payload.data
}
}
}
default: {
return state
}
}
}
export const Creators = {
getUserSuccess: user => ({
type: Types.GET_SUCCESS,
payload: {
user,
},
}),
getUserRequest: () => ({
type: Types.GET_REQUEST,
payload: {}
}),
getUserFailure: error => ({
type: Types.GET_FAILURE,
payload: {
error,
isStateEmpty: true
}
}),
rehydrateUserSuccess: user => ({
type: Types.REHYDRATE_SUCCESS,
payload: {
user
}
}),
rehydrateUserFailure: error => ({
type: Types.REHYDRATE_FAILURE,
payload: {
error
}
}),
updateUser: data => ({
type: Types.UPDATE_USER,
payload: {
data
}
}),
}
My user saga:
import { Types } from '../ducks/user'
import { call, put, takeLatest } from 'redux-saga/effects'
import { Creators as UserActions } from '../ducks/user'
import API from 'utils/api'
import DAO from '../../utils/dao';
function formatUsuarioToDbKeys(usuario, pk_pessoa) {
return {
pk_user: usuario.id,
fk_pessoa: pk_pessoa,
username_usu: usuario.username,
email_usu: usuario.email,
first_name_usu: usuario.first_name,
las_name_usu: usuario.last_name,
ativo_usu: usuario.ativo,
}
}
function formatPessoaToDbKeys(pessoa) {
return {
pk_pessoa: pessoa.pessoa_id,
fk_funcao: pessoa.funcao_id,
nome_pes: pessoa.nome,
codigo_erp_pes: pessoa.codigo_erp,
ativo_pes: pessoa.ativa,
}
}
function formatFuncaoToDbKeys(funcao) {
return {
pk_funcao: funcao.id,
nome_fun: funcao.nome,
}
}
function formatEquipeToDbKeys(equipe) {
return {
pk_equipe: equipe.id,
nome_equ: equipe.nome,
ativo_equ: equipe.ativo,
}
}
function* getUser() {
try {
const dados = yield call(API.getInstance().getRequest, '/initializer/?tipo=1')
const funcao = yield call(formatFuncaoToDbKeys, dados.funcao)
const pessoa = yield call(formatPessoaToDbKeys, dados.pessoa)
const usuario = yield call(formatUsuarioToDbKeys, ...[dados.usuario, dados.pessoa.pessoa_id])
const equipe = yield call(formatEquipeToDbKeys, dados.equipe)
yield put(UserActions.getUserSuccess({ usuario, pessoa, funcao, equipe }))
yield call(DAO.getInstance().createFuncao, funcao)
yield call(DAO.getInstance().createPessoa, pessoa)
yield call(DAO.getInstance().createUsuario, usuario)
yield call(DAO.getInstance().createEquipe, equipe)
} catch (error) {
yield put(UserActions.getUserFailure('Ocorreu um erro ao buscar dados do usuário.'))
yield call(console.warn, error.message)
}
}
function* rehydrateUser(action) {
if (action) {
try {
const user = yield call(DAO.getInstance().getUsuario)
yield put(UserActions.rehydrateUserSuccess(user))
} catch (error) {
yield put(UserActions.rehydrateUserFailure('Ocorreu um erro ao buscar dados do usuário.'))
yield call(console.warn, error.message)
}
}
}
export default sagas = [
takeLatest(Types.GET_REQUEST, getUser),
takeLatest(Types.GET_FAILURE, rehydrateUser)
]
and, finally this is my component:
class Dashboard extends Component {
...state and others methods
componentDidMount() {
this.props.getUserRequest()
this.props.getConstructionsRequest()
}
render() {
const spin = this.state.spin_value.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
return (
<View style={{ flex: 1 }}>
<StatusBar
hidden={false}
backgroundColor={colors.blueDarker} />
<ScrollView
ref={'refScrollView'}
stickyHeaderIndices={[2]}
style={styles.container}>
<ImageBackground
source={require('imgs/capa_dashboard.png')}
blurRadius={4}
style={styles.imageBackground}>
<View style={styles.headerContainer}>
<Text style={styles.textVersion}>{require('../../../package.json').version}</Text>
<Animated.View style={{ transform: [{ rotate: spin }] }}>
<TouchableOpacity
onPress={() => {
this._spinIcon()
this._handleRefresh()
}}>
<MaterialIcons
name={'refresh'}
size={35}
style={styles.refreshIcon}
color={'white'} />
</TouchableOpacity>
</Animated.View>
</View>
<View style={styles.headerButtonsContainer}>
<HeaderLeft
navigation={this.props.navigation} />
<Image
style={styles.image}
source={require('imgs/logo_header.png')} />
<HeaderRight />
</View>
<View style={{ flex: 1, justifyContent: 'center' }}>
{!this.props.user.status.loading && !!Object.keys(this.props.user.data).length
? <View>
<Text style={[styles.textNome, { textAlign: 'center' }]}>{this.props.user.data.pessoa.nome_pes}</Text>
<Text style={styles.textAuxiliar}>{this.props.user.data.funcao.pk_funcao == 1 ? this.props.user.data.equipe.nome_equ : 'Engenheiro(a)'}</Text>
</View>
: <ActivityIndicator style={{ alignSelf: 'center' }} size={'large'} color={colors.blue} />
}
</View>
</ImageBackground>
<ActionsButtons
stylesButton1={{ borderBottomColor: this.state.button_obras_selected ? 'white' : colors.transparent }}
stylesText1={{ color: this.state.button_obras_selected ? 'white' : 'lightgrey' }}
numberButton1={this.props.count_obras}
callbackButton1={async () => this.setState({ button_obras_selected: true })}
stylesButton2={{ borderBottomColor: !this.state.button_obras_selected ? 'white' : colors.transparent }}
stylesText2={{ color: !this.state.button_obras_selected ? 'white' : 'lightgrey' }}
numberButton2={this.state.count_enviar}
callbackButton2={async () => this.setState({ button_obras_selected: false })}
/>
<View>
{this.state.button_obras_selected
? <View style={styles.containerTextInput} >
<TextInput
onEndEditing={() => this._handleEndSearch()}
onFocus={() => this._handleStartSearch()}
ref={'textInputRef'}
style={styles.textInput}
onChangeText={search_text => this._filterSearch(search_text)}
autoCapitalize="none"
onSubmitEditing={() => this._handleClickSearch()}
underlineColorAndroid='rgba(255,255,255,0.0)'
placeholder={'Buscar obras...'} />
<TouchableOpacity
onPress={() => this._handleClickSearch()}
style={styles.searchImage}>
<MaterialCommunityIcons
name={'magnify'}
size={30}
color={'black'} />
</TouchableOpacity>
</View >
: null}
</View>
<View>
{this.state.button_obras_selected
? !this.props.constructions.status.loading && !!Object.keys(this.props.constructions.data).length
? <View style={{ flex: 1, marginTop: 8 }}>
<FlatList
data={this.props.constructions.data}
removeClippedSubviews={true}
keyExtractor={item => item.pk_obra.toString()}
renderItem={obra =>
<ObraComponent
callback={this._callbackObra}
item={obra.item}
isCurrentObra={nome => this.state.obra_atual == nome}
/>
}
/>
</View>
: <ActivityIndicator style={{ marginTop: 150 }} size={50} color={colors.blueDarker} />
:
<View style={{
flex: 1,
marginTop: 8,
}}>
<FlatList
data={[]}
removeClippedSubviews={true}
keyExtractor={item => item.fk_obra.toString()}
renderItem={acao =>
<AcaoComponent item={acao.item} />
}
/>
</View>
}
</View>
</ScrollView>
</View>
)
}
}
const mapStateToProps = state => ({
user: state.user,
constructions: state.constructions,
count_obras: state.constructions.data.length
})
const mapDispatchToProps = dispatch =>
bindActionCreators({
...UserActions,
...ConstructionsActions.DEEP,
...ConstructionsActions.SHALLOW
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(Dashboard)
As I said before, I call getUserRequest() to fire GET_REQUEST action on componentDidMount() but, when the request is done and my redux store is updated, my screen stays loading like this, even if the loading state in the store is false
https://user-images.githubusercontent.com/27635248/54752551-4e88c880-4bbd-11e9-9164-5fad46db6c13.png
I've used very similar store and saga structures in other projects, but for some reason, in this specific one, I haven't been able to figure it out
When I press one of these buttons, the component updates it's state and this triggers a re-render, then finally the data from my store shows up as props in the censored areas - this was the only way I figured out to re-render stuff with redux(in this project at least), forcing a re-render by updating the components state using this.setState():
https://user-images.githubusercontent.com/27635248/54752771-fef6cc80-4bbd-11e9-9d15-a46acb87f3a4.png
I'm not sure what's wrong with my implementation, i've checked out other similiar issues, none of them seemed to work, if anyone has a suggestion, feel welcome to post them below, thank you in advance.
"axios": "^0.18.0",
"react-native": "~0.55.2",
"react-redux": "^6.0.0",
"reactotron-redux": "^2.1.3",
"reactotron-redux-saga": "^4.0.1",
"redux": "^4.0.1",
"redux-saga": "^1.0.1",
EDIT:
So, as far as my understanding goes, updating redux's store reflectes on new props to the connected components, and React does update them based on a shallow compare. I've experimented a bit, and for some reason, my actions are triggered, sagas run fine, and the store is changing successfuly, but mapStateToProps is not being called after my success action is triggered.
Basically I'm not able to choose if the component should update (using shouldComponentUpdate to compare prevProps and this.props) because the component is not even receiving new props from mapStateToProps.
We have the following code within I have the method createDrawerNavigator in my App.js file
const RootDrawer = createDrawerNavigator({
Home: { screen: HomeScreen },
Detail: { screen: DetailScreen },
Result: { screen: ResultScreen },
Section : { screen: SectionScreen }
},{
contentComponent : ({ navigation }) => (<SideBar navigation={navigation} />),
initialRouteName: 'Home',
navigationOptions: {
headerStyle: {
backgroundColor: '#26272d',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
},
},
transitionConfig: () => ({
transitionSpec: {
duration: 500,
easing: Easing.out(Easing.poly(4)),
timing: Animated.timing,
},
screenInterpolator: sceneProps => {
const { layout, position, scene } = sceneProps;
const { index } = scene;
const height = layout.initHeight;
const translateY = position.interpolate({
inputRange: [index - 1, index, index + 1],
outputRange: [height, 0, 0],
});
const opacity = position.interpolate({
inputRange: [index - 1, index - 0.99, index],
outputRange: [0, 1, 1],
});
return { opacity, transform: [{ translateY }] };
},
}),
});
And I have the screen SideBar that acts as Drawer:
import React, { Component, PureComponent } from 'react';
import { connect } from 'react-redux';
import { Image, StyleSheet, View, TouchableOpacity, Text, Linking } from 'react-native';
import { Icon } from 'native-base';
import {StackActions, NavigationActions, DrawerActions} from 'react-navigation';
import Ionicons from 'react-native-vector-icons/Ionicons';
export default class SideBar extends React.Component {
goTo = (section) => {
const resetAction = StackActions.reset({
index: 0,
actions: [
NavigationActions.navigate({ routeName: 'Section' })
]
})
return () => this.props.navigation.dispatch(resetAction);
}
render() {
return(
<View style={styles.container}>
<View>
<View style={styles.logo}><Image source={require('./images/ln-header-bg.jpg')} style={styles.ln_logo} resizeMode="contain" /></View>
<TouchableOpacity style={styles.link_menu} onPress={() => { this.goTo('all'); }}><Text style={styles.link_menu_text}>Últimas noticias</Text></TouchableOpacity>
<TouchableOpacity style={styles.link_menu} onPress={() => { this.goTo(68); }}><Text style={styles.link_menu_text}>La Nación</Text></TouchableOpacity>
<TouchableOpacity style={styles.link_menu} onPress={() => { this.goTo(69); }}><Text style={styles.link_menu_text}>El Mundo</Text></TouchableOpacity>
<TouchableOpacity style={styles.link_menu} onPress={() => { this.goTo(70); }}><Text style={styles.link_menu_text}>Gente</Text></TouchableOpacity>
<TouchableOpacity style={styles.link_menu} onPress={() => { this.goTo(97); }}><Text style={styles.link_menu_text}>#YoParticipo</Text></TouchableOpacity>
</View>
<View>
<Text style={styles.follow_social}>Síguenos en las redes</Text>
<View style={styles.follow_social_links}>
</View>
</View>
</View>
)
}
}
In the SideBar I want to call an function located in Home Component, I tried with react navigation dispacth method but doesn't working.
What I have to call the function or navigate to another screen? Can some help me please?
Thanks!
I never used drawers from react-navigation, but I would assume that the way they work is similar to stackNavigators. So, assuming that, what you could do was to set a navigation parameter in the Home screen, for example, inside the componentDidMount() method, like so:
this.props.navigation.setParams({ 'paramName': paramValue });
and then, in the drawer, in the componentWillMount() method, you could do something like:
const var_x = this.props.navigation.getParam('paramName', null);
This way, you can either send the function itself as a parameter, or send a reference to the Home screen, and then access its methods from the drawer.
ps: on both calls, paramName needs to be a string.
ps2: in the getParam method call, the second argument, in the example, null, is the default value in case there is not a value for the requested parameter.
Again, I use this method for stackNavigators, so you might take a look at the react-navigation documentation to double check if there is any difference for drawer: https://reactnavigation.org/docs/en/drawer-navigator.html#docsNav
I'm making a list view were I will view a list of some data from my database. But after running the program all I got is white background screen. Does anyone knows the solution?
screen shot
Here is my code
export default class Pasta extends Component {
constructor() {
super()
this.state = {
dataSource: []
}
}
renderItem = ({ item }) => {
return (
<View style = {{flex: 1, flexDirection: 'row'}}>
<View style = {{flex: 1, justifyContent: 'center'}}>
<Text>
{item.menu_desc}
</Text>
<Text>
{item.menu_price}
</Text>
</View>
</View>
)
}
componentDidMount() {
const url = 'http://192.***.***.***:9090/menu'
fetch(url)
.then((response) => response.json())
.then((responseJson) => {
this.setState({
dataSource: responseJson.menu
})
})
}
render() {
return (
<View style = { styles.container }>
<FlatList
data = { this.state.dataSource }
renderItem = {this.renderItem}
/>
</View>
);
}
}
Add extraData prop to your FlatList to cause a re-render
keyExtractor = (item, index) => item.id; // note: id is the unique key for each item
render() {
return (
<FlatList
data = {this.state.dataSource}
renderItem = {this.renderItem}
extraData={this.state}
keyExtractor={this.keyExtractor}
/>
);
}
Also log and verify your data is present. I suggest referring to FlatList docs for more props like keyExtractor etc.
I try to fetch data from mySQL based on the 'Type' selected on Picker and update the ListView with the fetched data. So, I specify everything when the Picker's onValueChange will call the componentDidMount() function to fetch new data and update the ListView.
The problem is, when I select TypeA in Picker, the data in the ListView does not update, but when I select TypeB in Picker for the second time, the ListView updated based on TypeA. Then, I select TypeC, the ListView updated based on TypeB.
Is there is a problem with my code or I using the wrong method to do so?
export default class ProfileScreen extends Component {
constructor(props) {
super(props);
this.state = {
TypeInput : 'Vegetarian',
isLoading: true,
};
}
static navigationOptions = {
title: 'View/Edit',
};
OpenSecondActivity(id) {
this.props.navigation.navigate('Second', { ListViewClickItemHolder: id });
}
componentDidMount() {
return fetch('https://unsurfaced-cross.000webhostapp.com/getRecipeRN.php',{
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
RecipeType: this.state.TypeInput
})
}).then((response) => response.json())
.then((responseJson) => {
// Fetch Data update the List View Content
let ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
this.setState({
isLoading: false,
dataSource: ds.cloneWithRows(responseJson),
});
})
.catch((error) => {
console.error(error);
});
}
ListViewItemSeparator = () => {
return (
<View
style={{
height: .5,
width: "100%",
backgroundColor: "#000",
}}
/>
);
}
render() {
const { navigate } = this.props.navigation;
if (this.state.isLoading) {
return (
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
);
}
return (
<View style={styles.container}>
<Text style={styles.text}>Recipe Type: </Text>
<Picker
style={{width: 200}}
selectedValue={this.state.TypeInput}
onValueChange={
(itemValue, itemIndex) => {
this.setState({TypeInput: itemValue, isLoading:true})
this.componentDidMount()
this.forceUpdate()
}
}>
<Picker.Item label="Vegetarian" value="Vegetarian" />
<Picker.Item label="Fast Food" value="Fast Food" />
<Picker.Item label="Healthy" value="Healthy" />
<Picker.Item label="No-Cook" value="No-Cook" />
<Picker.Item label="Make Ahead" value="Make Ahead" />
</Picker>
<ListView
dataSource={this.state.dataSource}
renderSeparator= {this.ListViewItemSeparator}
renderRow={(rowData) => <Text style={styles.rowViewContainer}
onPress={this.OpenSecondActivity.bind(this, rowData.RecipeID)}> {rowData.RecipeName} </Text>}
/>
</View>
)
}
}
You are manually calling componentDidMount() which I think is wrong practice.
According to docs
componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
In your case what I would suggest is when you are changing the value in picker, have some different handler function which will take care of updating the state. You should not manually call componentDidMount(), as it is one of the lifecycle hooks of the component.
More on Component Lifecycle.
Above code now seems to be fine, in your code, once you receive the updated data, you can update the datasource of the ListView as below,
...
fetch('https://unsurfaced-cross.000webhostapp.com/createRN.php', {
...
.then((responseJson) => {
this.setState({dataSource: responseJson})
}
...
And you ListView, should be something like this
<ListView
automaticallyAdjustContentInsets={false}
dataSource={ds.cloneWithRows(this.state.dataSource)}
renderRow={(rowData)=> {
return <View>
<Text style={rowStyle.label} content={rowData.someField} />
</View>
}}
...
/>
Seem like the better way to deal with this issue is playing around with componentDidUpdate() instead of componentDidMount()
Can try refer to my completed application here below.
Github: https://github.com/slnn3r/RecipeRedux.git
The Project above:
is using Redux Architecture
using PHP mySQL database
using Stack Navigation
I'm currently learning react-native and getting stuck in ListView problems. I want to search data in ListView from a TextInput and i expect that the result is in ListView too.
Here's what i've done so far:
var PageOne = React.createClass({
getInitialState:function(){
return{
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2,
}),
loaded: false,
colorProps: {
titleColor: 'white',
},
searchText:"",
}
},
componentDidMount() {
this.fetchData();
},
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
loaded: true,
});
})
.done();
},
setSearchText(action){
let searchText = event.nativeEvent.text;
this.setState({searchText});
/*
*
*
*/
},
render() {
return (
<View style={{ flex: 1 }}>
<ToolbarAndroid
title="Movies"
{...this.state.colorProps}
style={{height:40, backgroundColor:'blue'}}
/>
<TextInput
placeholder="Search movies......."
value={this.state.searchText}
onChange={this.setSearchText.bind(this)} />
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderMovie}
style={styles.listView}
/>
</View>
);
},
renderMovie(movie) {
return (
<TouchableOpacity onPress={this._handlePressList.bind(this, movie)}>
<View style={styles.container}>
<Image
source={{uri: movie.posters.thumbnail}}
style={styles.thumbnail}
/>
<View style={styles.rightContainer}>
<Text style={styles.title}>{movie.title}</Text>
<Text style={styles.year}>{movie.year}</Text>
</View>
</View>
</TouchableOpacity>
);
},
what am i supposed to do next? Please help. Thanks :)
Update! After read the answer from urbancvek, i add function in setSearchText() method like this:
setSearchText(event){
const searchText = event.nativeEvent.text;
moviesLength = this.state.movies.length;
aMovie = this.state.movies;
const filteredMovies = this.state.movies.filter(checkTitle);
console.log("movies: " + JSON.stringify(filteredMovies));
function checkTitle() {
for(i=0;i<moviesLength;i++){
if(aMovie[i].title === searchText){
console.log("found: " + aMovie[i].title);
return aMovie[i];
}
}
}
this.setState({
searchText,
dataSource: this.state.dataSource.cloneWithRows(filteredMovies),
});
},
But it always show me all the movies, not filtered one. any ideas? thanks
In your fetchData method you should probably save responseData to state too. You will then interact with this data each time search field changes.
fetchData() {
fetch(REQUEST_URL)
.then((response) => response.json())
.then((responseData) => {
this.setState({
dataSource: this.state.dataSource.cloneWithRows(responseData.movies),
movies: responseData.movies,
loaded: true,
});
}).done();
},
Now in your setSearchText() method you should include some filter function that will find the movies you want from the movies you saved to state in fetchData().
setSearchText(action){
const searchText = event.nativeEvent.text;
const filteredMovies = this.state.movies.filter(/* awesome filter function */);
this.setState({
searchText,
dataSource: this.state.dataSource.cloneWithRows(filteredMovies);
});
},
Each time you want to update ListView you have to update it's dataSource. Only this way ListView component can realize that the data it's displaying has changed.
Hope I helped.
Searching data in the listview is basically just searching for it in a linked list or an array just take the input and search for it in the datasource or data blob. You can use linear search or binary search whichever you prefer.
The UIExplorer example from Facebook shows how to do this with ListView:
https://github.com/facebook/react-native/tree/master/Examples/UIExplorer