I have been struggling for the last 24 hrs about this one.
The problem is that, I am passing props in my parent class and calling the props data in my child component. When I check in render method the data shows correctly, but when calling it in componentDidMount it shows null.
Here is the sample of my code
class parent extends React.Component{
constructor(props){
super(props);
this.state = {
isOpen: false,
day:'monday',
data:[],
data1:[]
}
}
back () {
this.props.navigator.pop();
}
Mon(){
var record=[];
this.state.data.map((data) => {
if(data.frequency[0].day == 'Monday'){
record.push(data);
}
});
this.setState({data1:record});
}
componentWillMount() {
this.fetchData();
}
fetchData() {
//here i am getting the data from service and set the data to the state
}
render(){
return(
<View style={styles.Maincontainer}>
<View style={styles.navbar}>
<View style={styles.navbarContainer}>
<View style={styles.navbarIcon}>
<TouchableOpacity style={styles.button} onPress={() => this.back()}>
<Image
style={styles.menu2}
source={require('image!nav_arrow')} />
<Text style={styles.navbuttonText}>Back</Text>
</TouchableOpacity>
</View>
<View style={styles.navbarText}>
<Text style={styles.title}>Title</Text>
</View>
<View style={styles.navbarButton}>
</View>
</View>
</View>
<View style={styles.subMainContainer}>
<View style={styles.weekHeader}>
<View style={styles.weekRow}>
<Text style={styles.text} onPress={this.Mon.bind(this)}>Mon</Text>
</View>
</View>
<View style={styles.listBody}>
{this.state.data1.length == 0 ?(
<List data={this.state.data} />
) : (
<List data={this.state.data1}/>
)}
</View>
</View>
</View>
);
}
}
In my child component, I want to display the data in a ListView which is taken from the parent component
class List extends React.Component{
constructor(props){
super(props);
this.state = {
dataSource: new ListView.DataSource({
rowHasChanged: (row1, row2) => row1 !== row2
}),
isLoading: true
};
}
componentDidMount(){ //**here props data shows empty**
alert("didMount:"+JSON.stringify(this.props.data));
var data=this.props.data
this.setState({
dataSource: this.state.dataSource.cloneWithRows(data),
isLoading: false
});
}
render() { //**In this alert it shows correct data**
alert("data:"+JSON.stringify(this.props.data));
if (this.state.isLoading) {
return this.renderLoadingView();
}
return(
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderSchedule.bind(this)}
automaticallyAdjustContentInsets={false}/>
);
}
renderLoadingView(){
return(
<View style={styles.loading}>
<ActivityIndicatorIOS
size='large'/>
<Text>
Loading Schedule...
</Text>
</View>
);
}
renderSchedule(data){
return(
<View style={styles.ListContainer}>
<View style={styles.subContainer}>
<Text style={styles.classText}>Class Name: {data.name}</Text>
{data.frequency[0].day == null ? (
<Text style={styles.Day}>Day: {data.frequency[0].date}</Text>
):
<Text style={styles.Day}>Day: {data.frequency[0].day}</Text>
}
</View>
<View style={styles.subContainer1}>
<Text style={styles.startTime}>Start: {data.frequency[0].from}</Text>
<Text style={styles.endTime}>End: {data.frequency[0].to}</Text>
</View>
</View>
)
}
}
Here what i want is that initially i need to display the total data,when click on the text parent class the data need to be changed.I want to show the data in ListView.I already explain above about my problem,So can any one give me suggestions that how to solve my problem, Any help much appreciated
My guess would be that the alert in componentDidMount() is empty because it only called once, at a time when data is still empty, while render() is called on each change of state or props.
I would expect the flow of alerts to look something like this:
Alert called from child's render: "data: (empty)"
Alert called from child's didMount: "didMount:(empty)"
User clicks something in parent, state change, re-render triggered
Alert called from child's render: "data: FOO-content"
React does NOT call componentDidMount() on the second pass. If you want your datasource to be populated on EACH update of the child, you will need to add a componentDidUpdate() method to your child, with copy of the code you have inside componentDidMount().
As a general note: I would advise you to read React's explanation of component lifecycle and methods. I have a feeling that the preparation you are doing inside componentDidMount() is probably better placed elsewhere:
creating a DataSource and initial fill with data from props: in getInitialState()
updating Datasource with data from new props: in componentWillReceiveProps()
I found in React generally that if I want to prepare the initial state of my component based on this.props then I should do it in componentDidUpdate instead of componentDidMount. Components aren't mounted with populated props from the parent component, only updated with them.
I noticed this when doing normal React and doing console.log(this.props) in the render method of my child component. It would log initally as empty, then log again populated with the props. If I called a this.setState in componentDidMount then the population of this.props occurs in the third render. This is because calling this.setState in componentDidMount triggered a pre-update render.
I hope this makes sense and applies to your situation - I arrived at your question looking for a solution to similar behaviour.
Related
I'm trying to change class attribute in a function, I have a button and I want to change the text as soon as the user clicks it.
I wrote a class and everything but when the user clicks the button nothing happens!
I console logged to see if the variables actually changed and they did so why does it happen?
Here is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
loading: false,
buttonStatus: 'Post room',
value: '',
}
}
PostANewRoomScreen = () => {
let {data} = '';
return (
<View style={{padding: 15}}>
<TouchableOpacity
style={styles.button}
disabled={this.state.loading}
onPress={() => this.postroom(data)}
>
<Text style={{color: "white" , fontSize: hp('2.5%'), padding: 5}}>
{this.state.loading && <Animated.Image style={{height: hp('3%'), width: wp('2%')}} source={{uri: 'https://media0.giphy.com/media/JTVkOqJ1RyYEBnyoRb/giphy.gif'}} />}
{this.state.buttonStatus}
</Text>
</TouchableOpacity>
</View>
postroom = (value) => {
this.state.loading = true;
this.state.buttonStatus = "button clicked!";
}
this.state.loading = true;
this.state.buttonStatus = "button clicked!";
This does not re render component. You have to use useState Hook (in funcation component) or setState() (in class component) in order to update the state. I suggest converting component to functional component and use hooks to manage state.
const [buttonStatus, setButtonStatus] = useState('Post Room');
then use setButtonStatus(value) inside your postroom function.
Please refer react doc for useSate Hooks
Further, Your existing code has some issues. you are trying to access and mutate state of App component. if you need to share the component state try to lift the state up. If you need to pass properties to component use props.
So just getting started with react-native. I have a stateful component built as below :
class LoginView extends Component {
constructor(props) {
super(props);
this.state = {
inProgress: false
};
}
processLogin(){
this.setState({
inProgress: true
})
}
render() {
return (
<View style={styles.mainContainer}>
<View style={styles.loginContainer}>
<TextInput style={styles.input} placeholder="Password" />
<TouchableOpacity style={styles.button} onPress={() => this.processLogin()}>
{!this.state.inProgress && <Text style={styles.button_text}>Log in</Text>}/>}
</TouchableOpacity>
</View>
</View>
);}}
As you can see, i am binding the onPress event of the touchable opacity to the function processLogin. But for some reason, the function isn't fired!
The state doesn't change, also tried console.log but didn't fire either. Made sure that TouchableOpacity is imported from React-Native.
I am guessing that i messed up the binding. Any leads would be appreciated!
Note: Have only been trying this in the emulator, not in a real device yet.
instead of using the arrow function on onPress prop, use it when you are declaring the business function processLogin
So concretly, replace the declaration of this method by :
processLogin = () => {
this.setState({
inProgress : true
})
}
Then on onPress method, just change make it like this :
onPress = {this.processLogin}
Contrarily, you refer to the wrong this
Hope it's gonna help you sir
Regards.
I have this code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
}
this._handleRenderItem = this._handleRenderItem.bind(this);
this._keyExtractor = this._keyExtractor.bind(this);
}
componentDidMount() {
let success = (response) => {
this.setState({ dataSource: response.data });
};
let error = (err) => {
console.log(err.response);
};
listarProdutos(success, error);
}
_keyExtractor = (item, index) => item._id;
_handleRenderItem = (produto) => {
return (
<ItemAtualizado item={produto.item} />
);
}
render() {
return (
<Container style={styles.container}>
<Content>
<Card>
<CardItem style={{ flexDirection: 'column' }}>
<Text style={{ color: '#323232' }}>Produtos atualizados recentemente</Text>
<View style={{ width: '100%' }}>
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={this._handleRenderItem}
/>
</View>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
export default Home;
The function _handleRenderItem() is being called twice and I can't find the reason. The first time the values inside my <ItemAtualizado /> are empty, but the second was an object.
This is normal RN behavior. At first, when the component is created you have an empty DataSource ([]) so the FlatList is rendered with that.
After that, componentDidMount triggers and loads the updated data, which updates the DataSource.
Then, you update the state with the setState which triggers a re render to update the FlatList.
All normal here. If you want to try, load the datasource in the constructor and remove the loading in the componentDidMount. It should only trigger once.
If you want to control render actions, you can use shouldComponentUpdate method.
For example:
shouldComponentUpdate(nextProps, nextState){
if(this.state.friends.length === nextState.friends.lenght)
return false;
}
it will break render if friends count not change.
I recreated the issue in this snack. https://snack.expo.io/B1KoX-EUN - I confirmed you can use shouldComponentUpdate(nextProps, nextState) to diff this.state or this.props and return true/false - https://reactjs.org/docs/react-component.html#shouldcomponentupdate docs say this callback should be used only for optimization which is what we're doing here.
Error Message:
React: Warning setState Cannot update during an existing state
transition (such as within a render)
This is a react-native app, but I figure, this is more of a react question.
I'm getting the error described in the heading. But I'm puzzled about the reason and I'm not setting state in any of the parent or child components. So I have a grandchild component(CardLayoutResult), which renders a listView and each row renders a new component(Render row) which has a click event, upon clicking it calls a function(onselectLayout) from props(passed from the parent component(Personalization component)(parent function setCardTemplate). This parent function then calls setState of the parent internal state. Then re-rendering occurs.
Why am I getting this error upon click?
Parent Component
export class Personalization extends Component {
constructor(props) {
super(props);
this.state = {
showModel: true,
editorState: {}
};
this.setCardTemplate = this.setCardTemplate.bind(this);
}
setCardTemplate(selectedTemplateObj){
console.log(JSON.stringify(selectedTemplateObj));
this.setState({
showModel: false,
editorState: selectedTemplateObj
});
}
render() {
return (
<View>
<CreateCardStep containerState={{showModel:this.state.showModel ,
editorState: this.state.editorState
setCardTemplate:this.setCardTemplate}} />
</View>
)
}
Child Component
const CreateCardStep = (_props) => {
return (
<View>
<CardLayoutResults containerState={_props.containerState} />
</View>
);
}
Grand Child Component
export class CardLayoutResults extends Component {
constructor(props) {
super(props);
}
render() {
return(
<View>
<ListView
RenderRow = {(data) => <RenderRow styles={styles} rowObj={data} onSelectLayout={this.props.containerState.setCardTemplate} /> }
</View>
)
}
Render Row
const RenderRow = (props) => {
let base64Image = 'data:image/png;base64,'.concat(props.rowObj.base);
return (
<View style={props.styles.templateImage}>
<TouchableHighlight onPress={() => props.onSelectLayout(props.rowObj)}>
<Image style={props.styles.thumbnail} resizeMode= {Image.resizeMode.contain} source={{uri: base64Image}}/>
</TouchableHighlight>
</View>
);
};
- Update
This issue seems to be when setting state in the setCardTemplate function
You either need to bind your setCardTemplate function in the constructor or call it from a lambda function (binding is better)
<View>
<ListView renderRow = {(data => rowObj={data}} onSelectLayout={() => this.props.containerState.setCardTemplate} />
</View>
I guess the renderRow for listView is not called properly
Try
<View>
<ListView
renderRow = {(data) => <RenderRow rowObj={data} onSelectLayout={this.props.containerState.setCardTemplate} />}
/>
</View>
and pass down the props from personalization component like
return (
<View>
<CreateCardStep containerState={{showModel:this.state.showModel ,
editorState: this.state.editorState
setCardTemplate:(val) => this.setCardTemplate(val)}} />
</View>
)
The problem is with the way you are passing props to the child component from the parent.
In this snippet
<CreateCardStep containerState={{showModel:this.state.showModel ,
editorState: this.state.editorState
setCardTemplate:(val) => this.setCardTemplate(val)}} />
while passing the function as a prop itself you are calling the function so every time the component render the state will be set which is fine.
But when you click the Render Row component state is being set yet for the same property before finishing the render cycle which causes this warning.
To fix it just pass the function from parent as setCardTemplate: this.setCardTemplate. It will set the state (and internally will call the render) only on row press, not every time it is rendered
I am wanting to fire a function when state changes in my app. I have a modal with a button that changes state.
My home component (edited)
...
constructor(props) {
super(props);
this.state = {
color:'red',
modalVisible: false,
}
}
...
render() {
return (
<View>
<Modal
animationType={"slide"}
transparent={false}
visible={this.state.modalVisible}
>
<View style={{marginTop: 22}}>
<TouchableOpacity onPress={()=>this.setState({color:'blue'})}>
<Text>Blue</Text>
<TouchableOpacity>
<TouchableOpacity onPress={()=>this.setModalVisible(false)}>
<Text>Blue</Text>
<TouchableOpacity>
</View>
</Modal>
<Text style={{color:this.state.color}}>
Foo
</Text>
</View>
)
...
However when I close the modal, the color of my text is not changed to blue. It stays red until I open the modal back up, and then close again. Its like the state of 'color' is not being updated. I have ran into this before using redux and 'componentWillReceiveProps(nextProps)' but this has stumped me being in the same component.
I am using the native modal component from the docs. https://facebook.github.io/react-native/docs/modal.html
Touchable? TouchableHighlight?
I have not found any problem in your code except TouchaleHighlight tag.
https://rnplay.org/apps/fDGs3A