I'm mapping an array to be rendered in React Native. On an event (button press) I want to add and object to the array and it be rendered on the screen. I am getting the lifecycle functions to trigger, but not sure if they are needed for this or how to utilize them effectively. Any help would be greatly appreciated!
class Home extends Component {
constructor(props) {
super(props)
this.state = {
data: '',
text: '',
submitted: false,
count: 1,
arr: [
{
key: 1,
text: "text1"
},
],
}
buttonsListArr = this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
shouldComponentUpdate = () => {
return true;
}
componentWillUpdate = () => {
this.state.arr.push({key: this.state.count, text: 'text2'})
}
render() {
return (
<View style={styles.container}>
{buttonsListArr}
</View>
)
}
}
What you've written is not typical. A quick fix is to move the logic to a render function like
constructor(props) {
.
.
this.renderText = this.renderText.bind(this)
}
renderText() {
return this.state.arr.map(info => {
return (
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({count: this.state.count + 1})
}}/>
</View> )
})
}
then call the function within the render()
render() {
return (
<View style={styles.container}>
{this.renderText()}
</View>
)
}
You shouldn't be using a lifecycle call to add an element to an array. Simply call setState and it will rerender itself!
Try this.
<View style={styles.container}>
<Text key={info.key}>{info.text}</Text>
<Button title='Touch' onPress={() => {
this.setState({
count: this.state.count + 1
arr: this.state.arr.push({key: this.state.count, text: 'text2'})
})
}}/>
</View> )
return this.arrayholder.map(image => {
return (
<View style={styles.ImageContainer}>
<Image style={styles.ImageContainer} source={image} />
</View>
)
})
Related
import React from 'react';
import {View , Text, FlatList } from 'react-native';
class Global extends React.Component{
constructor(){
super();
this.state = {
global: [],
refreshing: false
}
}
renderItem = ({item}) => (
<View style={{ padding: 20, borderBottomWidth: 1, borderBottomColor: '#000'}}>
<Text> Positif: {item.confirmed.value} </Text>
<Text> Sembuh: {item.recovered} </Text>
<Text> Meninggal: {item.deaths} </Text>
</View>
)
onRefresh = () => {
this.getDataApi();
}
componentDidMount = () => {
this.getDataApi();
}
getDataApi = async () => {
this.setState({ refreshing: true})
fetch('https://covid19.mathdro.id/api')
.then(response => response.json())
.then(json => this.setState({ global: json.confirmed.value }))
}
render(){
console.log(this.state.global);
return (
<View>
<FlatList
data={this.state.global}
keyExtractor= {item => item.toString()}
renderItem= {this.renderItem}
refreshing={this.state.refreshing}
onRefresh={this.onRefresh}
/>
</View>
)
}
}
export default Global;
This is My code
The response you are getting from the API is an object and there is on need to use of flatlist as you are just showing three texts here, you can do something like this:
render(){
return (
<View style={{flex:1,justifyContent:"center",alignItems:"center"}}>
{this.state.global.confirmed != undefined ? <Text> Positif: {this.state.global.confirmed.value} </Text> : null}
{this.state.global.confirmed != undefined ? <Text> Sembuh: {this.state.global.recovered.value} </Text> : null}
{ this.state.global.confirmed != undefined ? <Text> Meninggal: {this.state.global.deaths.value} </Text> : null}
</View>
Hope this helps!
I have a parent component that maps through an array of chapters and renders (an exercise) a child component for every item found and passes an array of exercises to it.
class ExercisesScreen extends Component {
showSelectedItemList = (screenName, text) => {
Navigation.push("ExercisesStack", {
component: {
name: screenName,
options: navOptionsCreator(text)
}
});
};
get chapters() {
return this.props.chapters.map(chapter => (
<TouchableOpacity key={chapter.id}>
<ExercisesList
onPress={() =>
this.showSelectedItemList(chapter.screenName, chapter.name)
}
exercises={chapter.exercises}
/>
</TouchableOpacity>
));
}
render() {
return <View>{this.chapters}</View>;
}
}
const mapStateToProps = state => ({
chapters: chaptersSelector(state)
});
When this child component receives the array of exercises, it maps through it and renders a list of exercises.
class ExercisesList extends Component {
render() {
return this.props.exercises.map(exercise => (
<View key={exercise.id}>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.props.showSelectedItemList(exercise.screenName, exercise.name)
}
>
<Image source={exercise.icon}/>
<View>
<Text>{exercise.name}</Text>
</View>
<Image source={arrow} />
</TouchableOpacity>
<View />
</View>
));
}
}
ExercisesList.propTypes = {
onPress: PropTypes.func,
exercises: PropTypes.arrayOf(PropTypes.object)
};
The result I get from both components rendered simultaneously:
The question is, what should I do in order for them to render themselves separately and show the corresponding ExercisesList for every chapter in ExercisesScreen?
Make your child component ExercisesList as functional component that only show the corresponding ExercisesList for every chapter not perform any rendering.
Like below:
const ExercisesList = (props) => {
const { exercises } = props;
return({
exercises.map(exercise, index) => renderExcercise(exercise, index)
})
}
const renderExcercise = (exercise, index) => {
return(
<View key={exercise.id}>
<TouchableOpacity
style={styles.button}
onPress={() =>
this.props.showSelectedItemList(exercise.screenName, exercise.name)
}
>
<Image source={exercise.icon}/>
<View>
<Text>{exercise.name}</Text>
</View>
<Image source={arrow} />
</TouchableOpacity>
<View />
</View>
)
}
export default ExercisesList;
ExercisesList.propTypes = {
onPress: PropTypes.func,
exercises: PropTypes.arrayOf(PropTypes.object)
};
I have a page.js with different components that inside them make setState in componentDidMount(). And the render of parent execute every time (10 in total). But is a problem because I want execute a request on api rest on componentDidUpdate and this function execute very times.
There are any way for the setstate in a child not affecting the render of parent?
Update with code
page.js
import Header from 'Pages/userprofile/general/HeaderMenuUser';
import UserHeader from 'Pages/userprofile/basic/header/UserHeader';
import UserContent from 'Pages/userprofile/basic/content/UserContent';
...
class UserProfile extends Component {
static navigationOptions = ({navigation}) => {
const user = navigation.getParam('user', '');
return {
title: 'User Profile',
header: (navigation) => <Header {...navigation} title="My profile" user={user} origin="UserProfile" edit={false} />,
headerTintColor: '#000',
headerTitleStyle: {
fontWeight: 'bold',
},
};
};
getProfile(){
//const { user } = this.props.navigation.state.params;
//Profile.get(user.id).then( response => {
ProfileApi.get().then( response => {
this.setState({ user: response.user });
this.props.navigation.setParams({
user: response.user,
});
});
}
render() {
//console.warn(this.props.isFocused ? 'Focused' : 'Not focused');
return (
<ScrollView style={[styles.scroll, globalStyles.generalScrollContainer]}>
<View style={{backgroundColor:'#fff', minHeight: 50}}>
<GradientBackground />
<UserHeader user={this.state.user} edit={false}/>
</View>
{ typeof this.state.user === "object" && (
<View style={styles.container}>
<UserContent user={this.state.user} />
</View>
)}
</ScrollView>
);
}
}
export default withNavigationFocus(UserProfile);
UserContent.js
export default class UserContent extends Component {
updateState = (update) => {
if (update['simple']){
this.setState({ [update['key']]: update['value'] });
} else {
projects = this.state.user.projects;
projects.push(update['value']);
this.setState( prevState => ({
user: {
...prevState.user,
projects: projects
}
}));
this.setState( prevState => ({
user: {
...prevState.user,
can_create_projects: update['value'].can_create_projects
}
}));
Functions.storeItem('user', this.state.user);
}
};
componentDidMount() {
Functions.getApiToken().then((response) => {
this.setState({ url: url + response });
});
}
render() {
return (
<View>
<View style={styles.content}>
<UserBiography user={this.state.user} />
<View style={[globalStyles.rowBetween, globalStyles.rowVerticalCenter]}>
<Text style={globalStyles.titleText}>Projects</Text>
{ this.state.user.can_create_projects && (
<View>
<TouchableOpacity
style={styles.button}
onPress={() => { this.setState({collapsed: !this.state.collapsed}) }}>
<Text style={[globalStyles.textTag, this.state.collapsed ? globalStyles.backgroundBlue : globalStyles.backgroundGary ]}>{this.state.collapsed ? "Add" : "Close add"} project</Text>
</TouchableOpacity>
</View>
) }
</View>
<View style={[globalStyles.borderBottomView, {marginBottom: 30}]}></View>
<UserCreateProject collapsed={this.state.collapsed} updateState={this.updateState}/>
{typeof this.state.user === 'object' && this.state.user.projects.length > 0 && (this.state.user.projects.map((project, index) => {
return (<UserProject project={project} key={project.id} updateState={this.updateState} />)
}))}
</View>
</View>
)
}
}
You can use shouldComponentUpdate() method to control your render:
shouldComponentUpdate(nextProps, nextState) {
if (nextState.render_screen) {
return true;
} else {
return false;
}
}
I have this screen under a DrawerNavigator:
class myScreen extends Component {
state: Object;
constructor(props) {
super(props);
this.getBeers();
this.state = {
is_loading: true,
beers: null,
beers_ds: null,
}
}
componentWillUpdate(nextProps: Object, nextState: Object)
{
if(!nextState.is_loading)
{
let ds = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 });
nextState.beers_ds = ds.cloneWithRows(nextState.beers);
}
}
async getBeers()
{
let finalApiURL = API_BASE_URL;
let fetchResult = await fetch(finalApiURL)
let Result = await fetchResult.json();
this.setState({users: Result, is_loading: false});
}
render()
{
if(this.state.is_loading)
{
return(
<View style={{ flex: 1, backgroundColor: 'white', marginTop: 50 }}>
<Text>
Loading ...
</Text>
</View>
)
}
else
{
let renderRow = (row) => {
return (
<TouchableOpacity onPress={ () => {} }>
<View >
<Text>{row.name}</Text>
<Text style={{ color: 'blue' }}>ADD</Text>
</View>
</TouchableOpacity>
);
}
return
(
<View >
<ListView dataSource={this.state.beers_ds} renderRow={renderRow.bind(this)} />
</View>
);
}
}
}
export default myScreen;
Now when I get results back from the server, I setState(). Then, componentWillUpdate() gets called, and the else() statement in render() fires.
But my screen does not change and it stays on Loading ...
And sometimes I get see this error:
Can anyone help in finding why this weird behavior is occurring?
Thanks
EDIT:
It works when I change this:
return
(
To this:
return (
Welcome to Javascript!
I will assume that you have the latest react native so ListView is deprecated you can use FlatList (Dod) instead
change your code to be like this
renderRow = (row) => {
return (
<TouchableOpacity onPress={ () => {} }>
<View >
<Text>{row.name}</Text>
<Text style={{ color: 'blue' }}>ADD</Text>
</View>
</TouchableOpacity>
);
}
render() {
return(
<View style={{ flex: 1, backgroundColor: 'white', marginTop: 50 }}>
{this.state.is_loading ?
<Text>
Loading ...
</Text>
:
<FlatList data={this.state.users} renderItem={(item) => this.renderRow(item)} />
}
</View>
);
}
I am working on a simple to do application linked to firebase using react native. i have one class with a few methods in it,as far as I can tell by searching online about this problem, it seems to be something related to an output in the render function. but i checked my methods and I am unable to pin down the exact problem.
and this is my class:
class ToDo_React extends Component {
constructor(props) {
super(props);
var myFirebaseRef = new Firebase(' ');
this.itemsRef = myFirebaseRef.child('items');
this.state = {
newTodo: '',
todoSource: new ListView.DataSource({rowHasChanged: (row1, row2) => row1 !== row2})
};
this.items = [];
}
componentDidMount() {
// When a todo is added
this.itemsRef.on('child_added', (dataSnapshot) => {
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
this.itemsRef.on('child_removed', (dataSnapshot) => {
this.items = this.items.filter((x) => x.id !== dataSnapshot.key());
this.setState({
todoSource: this.state.todoSource.cloneWithRows(this.items)
});
});
}
addTodo() {
if (this.state.newTodo !== '') {
this.itemsRef.push({
todo: this.state.newTodo
});
this.setState({
newTodo : ''
});
}
}
removeTodo(rowData) {
this.itemsRef.child(rowData.id).remove();
}
render() { return (
<View style={styles.appContainer}>
<View style={styles.titleView}>
<Text style={styles.titleText}>
My Todos
</Text>
</View>
<View style={styles.inputcontainer}>
<TextInput style={styles.input} onChangeText={(text) => this.setState({newTodo: text})} value={this.state.newTodo}/>
<TouchableHighlight
style={styles.button}
onPress={() => this.addTodo()}
underlayColor='#dddddd'>
<Text style={styles.btnText}>Add!</Text>
</TouchableHighlight>
</View>
<ListView
dataSource={this.state.todoSource}
renderRow={this.renderRow.bind(this)} />
</View>
);
}
renderRow(rowData) {
return (
<TouchableHighlight
underlayColor='#dddddd'
onPress={() => this.removeTodo(rowData)}>
<View>
<View style={styles.row}>
<Text style={styles.todoText}>{rowData.text}</Text>
</View>
<View style={styles.separator} />
</View>
</TouchableHighlight>
);
}
}
The issue is with your renderRow method where you render:
<Text style={styles.todoText}>{rowData.text}</Text>
Here you are passing an object as a children of Text element instead of a string. That is because you're setting an object under the text key for your data store here:
this.items.push({id: dataSnapshot.key(), text: dataSnapshot.val()});
Note that val() here is a reference to an object you've pushed to your firebase instance here (see firebase docs):
this.itemsRef.push({
todo: this.state.newTodo
});
So what you perhaps want to do here is to just push this.state.newTodo instead of an object.