I have created a custom component which takes a color name from the parent component and updates that color in the state in the parent component. Currently, after I have done all the code, it does not save the new color, and therefore, does not update the the state.
This is for a react-native android app that I am building. I have looked at the ReactNative documentation for flatlist and textinput. I have looked at Stack overflow for solutions too
Set up a react native project. this is my parent component
class HomePage extends Component {
constructor(props) {
super(props);
this.state = {
backgroundColor: "blue",
availableColors: [
{name: 'red'}
]
}
this.changeColor = this.changeColor.bind(this)
this.newColor = this.newColor.bind(this)
}
changeColor(backgroundColor){
this.setState({
backgroundColor,
})
}
newColor(color){
const availableColors = [
...this.state.availableColors,
color
]
this.setState({
availableColors
})
}
renderHeader = ()=>{
return(
<ColorForm onNewColor={this.newColor} />
)
}
render() {
const { container, row, sample, text, button } = style
const { backgroundColor, availableColors } = this.state
return (
<View style={[container,{backgroundColor}, {flex: 1}]} >
<FlatList
data={availableColors}
renderItem={
({item}) =>
<ColorButton
backgroundColor={item.name}
onSelect={(color)=>{this.changeColor(color)}}>
{item.name}
</ColorButton>}
keyExtractor={(item, index) => index.toString()}
ListHeaderComponent={this.renderHeader}
>
</FlatList>
</View>
);
}
}
this is the code for ColorForm component
class ColorForm extends Component {
constructor(props) {
super(props);
this.state = {
txtColor:'',
}
this.submit = this.submit.bind(this)
}
submit() {
this.props.onNewColor(this.state.txtColor.toLowerCase())
this.setState({
txtColor: 'yellow',
})
}
render() {
const {container, txtInput, button} = style
return (
<View style={container}>
<TextInput style={txtInput}
placeholder="Enter a color"
onChangeText={(txtColor)=>this.setState({txtColor})}
value={this.state.txtColor}></TextInput>
<Text
style={button}
onPress={this.submit}>Add</Text>
</View> );
}
}
and below is the code for ColorButton component
export default ({backgroundColor, onSelect=f=>f}) => {
const {button, row, sample, text} = style
return (
<TouchableHighlight onPress={()=>{onSelect(backgroundColor)}} underlayColor="orange" style={button}>
<View style={row}>
<View style={[sample,{backgroundColor}]}></View>
<Text style={text}>{backgroundColor}</Text>
</View>
</TouchableHighlight>
)
}
The imports and stylesheets are setup as standard and do not effect the code so I have chosen to not show them.
EDIT: Adding the expo snack here.
Expected Behavior:
When I press "ADD" on the ColorForm component, it should take that color and add that to the this.state.availableColor array and therefore visible in the ColorButton component. And when I touch the button, it should make that change
Current behaviour:
When I enter a color and press on add, it makes an empty button in the ColorButton component - NOT the color i entered in the color I entered in the ColorForm component.
EDIT: Adding the expo snack here.
Your state is updating but the FlatList is not updating. Because your data={availableColors} in flatlist is not changing but your state is changing .
Try to add extraData
A marker property for telling the list to re-render (since it implements PureComponent). If any of your renderItem, Header, Footer, etc. functions depend on anything outside of the data prop, stick it here and treat it immutably.
Try this
<FlatList
extraData={this.state.backgroundColor}
Updated Answer
the problem is in this function newColor(color)
const availableColors = [
...this.state.availableColors,
color
]
you just receive a string of color but you have defined object like this {name: 'red'}
please use this code
newColor(color){
const availableColors = [
...this.state.availableColors,
{name: color}
]
this.setState({
availableColors
})
}
Snack link with example : https://snack.expo.io/#mehran.khan/healthy-cake-state-sample
Also add export default to main component to remove error of launch
export default class HomePage extends Component {
App Preview
I had many problems using setState() in the way you are using now. I recommend you to use in this way setState(), with a callback:
this.setState((previousState, currentProps) => {
return { ...previousState, foo: currentProps.bar };
});
This is one of the article that talks about it.
setState() does not always immediately update the component. It may
batch or defer the update until later.
From react website setState().
Related
When using a FlatList Component in react native I need to know when all the visible items have been rendered.
I am providing data and the renderItem when I get the componentDidMount I can see the FlatList there but because FlatList Component asynchronously renders each item they only show up after on componentDidUpdate. Also, this could (and probably will) include off-view items.
I would like to know if there is someone out there that has found a possible way to have a controlled process (not with setTimeout) of knowing when the visible items are fully rendered.
Thanks.
I ended up using the componentDidMount of the renderItem component as an indicator that element is rendered.
In the below example ActivityIndicator will be shown until the first 10 items are rendered (the number I set in the initialNumToRender).
This approach is not ideal. You may need to tweak it for your case but the general idea is shown below.
Here is the list item:
class ListItem extends React.Component<{ onRendered: () => void }> {
componentDidMount(): void {
this.props.onRendered();
}
render() {
return (
<View>
<Text>Item</Text>
</View>
);
}
}
And here is the screen with FlatList that uses above ListItem:
export class FlatListTestScreen extends React.Component<any, { creating: boolean }> {
constructor(props: any, context: any) {
super(props, context);
this.state = {
creating: true,
};
}
onRenderedItem = () => {
this.setState({
creating: false,
});
};
renderItem = (item: any) => {
return <ListItem onRendered={this.onRenderedItem} />;
};
dataArray = Array(20)
.fill('')
.map((_, i) => ({ key: `${i}`, text: `item #${i}` }));
render() {
const loader = this.state.creating ? <ActivityIndicator /> : <></>;
return (
<View>
{loader}
<FlatList
initialNumToRender={10}
data={ this.dataArray }
renderItem={ this.renderItem }
keyExtractor={ item => item.key }
/>
</View>
);
}
}
I also tried to use a map to collect the number of rendered items but on practice it seems that they all fire componentDidMount at the same time so simpler approach may be better.
I'm getting back into React and trying to replicate an instagram post. Variables that don't change such as the name of the poster, location, etc I think I have a solid understanding of how to handle. I keep in them in the chain of this.props as they will be immutable from the user perspective. But when it comes to something like "reactions", I am unsure of when to begin storing them in this.state vs. this.props due to the multiple levels of nesting.
Should I keep the data relating to my reactions array / object in this.state (as I attempt to do below) for all nested components all the way down? Or keep them in this.props until I get to the lowest child component?
I don't have any code for lifting state up since I want to make sure my architecture is correct first.
Home.js
const POST_DATA = [
{
name: 'Outdoors Guy',
location: 'Yosemite, CA',
id: '123',
body: 'Hello world!',
image: 'https://someurl',
reactions: [
{
reaction: 'Like',
count: 2,
selected: true
},
{
reaction: 'Love',
count: 1,
selected: false
}
]
}
];
export default class HomeScreen extends React.Component {
constructor(props){
super(props)
this.state = {
posts: POST_DATA
}
}
render(){
return (
<View style={styles.container}>
<StatusBar barStyle="dark-content" />
<ScrollView>
<FlatList styles={styles.list}
data={this.state.posts}
renderItem={({ item }) =>
<Post
name={item.name}
location={item.location}
body={item.body}
image={item.image}
reactions={item.reactions}/>}
keyExtractor={item => item.id}/>
</ScrollView>
</View>
);
}
}
Post.js
export default class Post extends Component {
constructor(props){
super(props)
state = {
// should I be doing this?
reactions: this.props.reactions
}
}
render() {
return (
<View>
<View style={styles.postHeader}>
// Example of these two variables ending their chaining here
<Text style={styles.name}>{this.props.name}</Text>
<Text style={styles.location}>{this.props.location}</Text>
</View>
<Text style={styles.body}>{this.props.body}</Text>
<Image style={styles.image} source={{uri: this.props.image}}/>
// Heres where the confusion begins
<ReactionList list={this.props.reactions}/>
</View>
);
}
}
ReactionList.js
export default class ReactionList extends Component {
constructor(props){
super(props)
state = {
reactions: this.props.reactions
}
}
render() {
if (this.state.reactions === null)
{
return (
<View>
<AddReaction/>
<FlatList styles={styles.reactionList}
data={this.state.reactions}
renderItem={({ item }) =>
<Reaction
reaction={item.reaction}
count={item.count}
selected={item.selected}/>}
keyExtractor={item => item.id}/>
</View>
)
}
else{
return (
<View>
<AddReaction/>
</View>
)
}
}
}
Reaction.js
export default class Reaction extends Component {
constructor(props){
super(props)
state = {
// I understand these are not visible in the component
// but still want to track the data
count: this.props.count,
selected: this.props.selected
}
}
_onSelectPress () {
this.setState({
selected: !this.state.selected,
count: ++this.state.count
})
}
render() {
return (
<TouchableOpacity style={styles.reaction}
onPress={() => this._onSelectPress()}>
<Text style={styles.reactionText}>{this.props.reaction}</Text>
</TouchableOpacity>
);
}
If it makes sense to you, you could store the POST_DATA completely inside the state, as this will greatly simplify how the Parent component who holds the post information and the Child Post component behave.
For example, what if, instead of just letting the user edit reactions, you wanted the post owner to be able to edit the body or location of POST_DATA? Or what if you wanted the reactions count to be accesible to say, a separate modal (when you click on the post)?
A way to future-proof this design, while keeping it simple, is to send the whole POST_DATA to your <Post> component and if you don't want to use a library such as Redux to handle the state, then lift the callback for edits up:
Here's a simplified Codesandbox I did of this implementation.
<InstaPost
post={this.state.post} <-- this.state.post holds POST_DATA
handleReactionClick={this.handleReactionClick}
handleBodyEdit={this.handleBodyEdit} <-- if we decide to implement this later on
/>
Why hold network data as an Immutable object in the app/component state?
The benefits of holding the post data in the state, as an Immutable object (in the example I used immer.js), is that if, for example, the way Instagram or Facebook work is that, when you click on the "Love" reaction, an API request is sent for your reaction, and it returns the value of the reactions updated real-time:
So for example, if I pressed on "Like (2)", and 10 more people pressed like before that, the API wouldn't return "3" (2+1), but rather, "13" (2 + 1 + 10). Handling this with your current design would require much more work than with the design I propose.
Personally, I would prefer using a library such as Redux for application state, but for a small prototype it's not necessary. I do, however, recommend always using immutable objects whatever the size of the project if network fetching/updating is involved.
Hope this helps!
Consider the following React Native code:
import React from 'react';
import {Text, View, StyleSheet, Button} from 'react-native';
export default class Target extends React.Component {
constructor(props){
super(props);
this.state ={ isLoading: true};
this.hitData = this.props.hitData;
}
componentDidMount(){
for(let i of Object.keys(this.hitData)){
if(this.hitData[i] === 0){
this.hitData[i] = '---'
}
}
this.setState({
prop1: this.hitData['prop1'],
prop2: this.hitData['prop2'],
prop3: this.hitData['prop3'],
prop4: this.hitData['prop4'],
prop5: this.hitData['prop5'],
isLoading: false
});
this.onPress = this.onPress.bind(this);
}
componentDidUpdate(prevProps) {
// console.log(prevProps)
if(this.props.hitData !== prevProps.hitData){
console.log("Component "+ this.state.mac + " got new props!");
this.hitData = this.props.hitData;
this.setState((state, props) =>{
let newState = {};
for(let i of Object.keys(props.hitData))
{
if(state[i] !== props.hitData[i])
{
console.log(i + " is different!");
newState[i] = props.hitData[i]
}
}
return newState
});
}
}
onPress(txt) {
console.log(txt);
}
render(){
return(
<View style={styles.container}>
<View style={{flex: 1, flexDirection: 'row'}}>
<View style={{borderWidth: 2.5, borderColor: '#00FF00',width: '50%'}}>
<Text style={{fontSize: 19, fontWeight: 'bold'}}>{this.state.prop1}</Text>
<Text style={{fontSize: 16}} numberOfLines={1}>{this.state.prop2}</Text>
</View>
<View>
<Text>{"Prop3: " + this.state.prop3}</Text>
<Text>{"Prop4: " + this.state.prop4}</Text>
<Text>{"Prop5: " + this.state.prop4}</Text>
</View>
</View>
<Button key={this.hitData[0]} title={"BUTTON"} onPress={() => this.onPress(this.hitData[0])}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
borderRadius: 4,
borderWidth: 2.5,
height: 100,
marginBottom: 5,
overflow: 'hidden'
}
});
This is the code for a React Native component. The component takes in information received from an EventSource in the parent element. The aim is to update the fields containing Prop[3-5]based on data from future events.
In the parent component I have a property per item on state and am defining each element like:
const targetList = this.state.filterKeys.map((item) =>
<Target key={item} hitData={this.state[item]['lastHit']}/>
In my Event handler I can then do:
this.setState({
[id]: hitIntermediate
})
to send new props to each child component.
In my child component I do see the new props arriving, but after the first update the child stops updating. It does not matter how many events I send, after the first update has been received no further updates are displayed.
The strange part is if I query this.state in the child component, I do see that the state reflects the data from the new event, but the display does not change.
Am I completely misunderstanding something here. Essentially what I am wanting to do is to do the equivalent of setting .innerHTML on the <Text> tags containing the various pieces of data, but all my updates seem to get ignored after the first.
I was able to resolve this on my own.
As it turns out, the way I was getting events into my component was silly.
I changed it so instead of maintaining a hugely complex state out in the main component, I how just pass in the EventSource and define an eventhandler inside each child component.
Now I am seeing updates in close to real-time. I would still like to improve re-render performance a little bit, but overall the issue I was having is resolved.
Now a quick bonus question: why is it I seem to see (slightly) faster performance if I have the react-devtools running?
I am trying to build a calendar using react-native-calendars library by wix.
For instance, when I click on 5 Dec, 2018 in Calendar Component, it should navigate to 5 Dec, 2018 on Agenda Component. This is what I am trying to do.
This is my code for Calendar Component where I am trying to grab the id and bind it:
class Calendar extends Component {
constructor(props) {
super(props);
this.state = {
active: 'true'
}
}
render() {
const {onSelect} = this.props;
return (
<View>
<CalendarList
keyExtractor = {day=>day.id}
onDayPress={(day) => {onSelect.bind(this, day)}}
pastScrollRange={12}
futureScrollRange={12}
scrollEnabled={true}
showScrollIndicator={true}
/>
</View>
);
}
}
Calendar.propTypes = {
onSelect: PropTypes.func,
}
This is my code CalendarScreen where I am doing the navigation.
const CalendarScreen = ({navigation}) => (
<View >
<Calendar navigation={navigation}
onSelect={(id)=>{
navigation.navigate('Agenda', {id}) }}>
<StatusBar backgroundColor="#28F1A6" />
</Calendar >
</View>
);
Calendar.propTypes = {
navigation: PropTypes.object.isRequired
}
export default CalendarScreen;
I also the solution mentioned here. But no luck.
EDIT:
This is my Agenda Component
class WeeklyAgenda extends Component {
constructor(props) {
super(props);
this.state = {
items: {}
};
}
render() {
return (
<View style={{height:600}}>
<Agenda
items={this.state.items}
loadItemsForMonth={this.loadItems.bind(this)}
selected={Date()}
renderItem={this.renderItem.bind(this)}
renderEmptyDate={this.renderEmptyDate.bind(this)}
rowHasChanged={this.rowHasChanged.bind(this)}
onRefresh = {() => { this.setState({refeshing : true})}}
refreshing = {this.state.refreshing}
refreshControl = {null}
pastScrollRange={1}
futureScrollRange = {3}
/>
);
}
This is AgendaScreen
const AgendaScreen = ({navigation}) => (
<View style={{height: 100}}>
<WeeklyAgenda navigation={navigation}>
<StatusBar backgroundColor="#28F1A6" />
</WeeklyAgenda >
</View>
);
WeeklyAgenda.propTypes = {
navigation: PropTypes.object.isRequired
}
export default AgendaScreen;
Calender passes CalenderScreen a callback onSelect(id).
Calender screen only needs to call onSelect(day).
onDayPress={(day) => {onSelect(day)}}
Or make the callback accept both the navigation object and the day
onDayPress={(day) => {onSelect(day, navigation)}}
Calender should now set
onSelect={(day, navigation)=>{
navigation.navigate('Agenda', {day}) }}>
Or as you are passing the navigation object to Calender why pass a callback
onDayPress={(day) => {this.props.navigation.navigate('Agenda', {day})}}
Edit: new classes, and changed id to day
Same problem. onSelect navigates to AgentScreen and passes it the navigation prop (witha the day object in it), and in turn, AgentScreen passes the navigation prop to WeeklyAgent, but WeeklyAgent doesn't use the navigation prop to get the id.
selected={Date()}
Creates a new date object with todays date.
To get the correct date you need to get the day from navigation, usually something along the lines of
this.props.navigation.state.params["day"]
returns the date passed.
Now if WeeklyAgenda doesn't actually use navigation further, i wouldn't pass it.
Instead Weekly agenda should have a prop 'day', so instead of
<WeeklyAgenda navigation={navigation}>
try
const { params } = this.props.navigation.state;
...
<WeeklyAgenda day={params["day"}>
The first line unpacks the params object from state into a variable called params.
WeeklyAgenda would now do
selected={this.props.day}
I don't think the problem here is the binding at all.
I'd recommend reading this article or another on what the binding does.
I have a button and when I click on it, button changes its state. But I have to double tap to change the state. I console logged it and on first click, it shows blank and when I click on it again, it will change its state. Below is my code:
class CustomButtonOne extends React.Component {
constructor(props) {
super(props);
this.state = {
buttonOne:'',
};
this.selectionOnPressOne = this.selectionOnPressOne.bind(this),
}
selectionOnPressOne = () => {
this.setState({
buttonOne:'ONE'
})
console.log(this.state.buttonOne, 'this.state.buttonOne')
}
render() {
return (
<View>
<TouchableOpacity
onPress={()=> this.selectionOnPressOne()}>
<Text>Button</Text>
</TouchableOpacity>
</View>
)
}
}
export default CustomButtonOne
Why is this happening? and How can I change the state with one tap?
Any advice or comments would be appreciated thanks!
The setState action is async, therefore the changes that you log may not be available immediately.
Try this
this.setState({
buttonOne:'ONE'
}, () => console.log(this.state.buttonOne, 'this.state.buttonOne'))