How to call componentWillUpdate() to update a child components state? - reactjs

My toolbar is already rendered and i want to now call componentWillUpdate() on this toolbar when i need to update it's state?
HomeScreen
constructor(){
super();
this.state = {
pagenum: 0,
toptoolbartitle: "default",
homeviewtitle: "default",
};
}
updateHomeViewTitle(viewtitle){
console.log(viewtitle);
this.state.homeviewtitle = viewtitle;
console.log("homeviewtitle is now"+this.state.homeviewtitle);
}
render() {
return (
<View style={styles.container}>
<View style={{flex:1}}>
<TopToolbarAndroid
defaulttitle={"default"} titleupdate={this.state.homeviewtitle}/>
<RNCamera updateviewtitle={this.updateHomeViewTitle.bind(this)} />
<BottomBar />
</View>
</View>
TopToolbarAndroid
constructor(props){
super();
this.props = props;
this.state = {
pagenum: 0,
title: this.props.defaulttitle
};
}
componentWillUpdate(){
console.log("component will update?");
if (this.props.titleupdate != null)
{console.log("updating toolbar title to: "+this.props.titleupdate);
this.setState({title: this.props.titleupdate});}
}
render() {
return (
<ToolbarAndroid
title= {this.state.title}
style={styles.toolbar}
/>
);
}
Result
The componentWillUpdate is not updating the toolbars title, its not being called (re-rendered).

You need not maintain state in TopToolbarAndroid.
Just do this tweak in TopToolbarAndroid to update title-
title={this.props.titleupdate ? this.props.titleupdate : this.props.defaulttitle}
Get rid of this.state too.
I'm not sure what this.props = props does in the constructor. I'd just do super(props).
Let me know, if this doesn't work. Please provide a link to runnable app as well, so that people can solve your problem effectively. Add a tag of react-native to your question to attract the right kind of people.
Host your example here - https://rnplay.org/

Related

React does not rerender when a prop changes

I'm trying to develop an edit mode for a application.
In edit mode some buttons should have a lower opacity.
A boolean variable stores if the edit mode is active. This variable is passed down to its childs using props. If I now change the editMode in the parents state, the childs are not being rerendered.
Parentclass:
export default class Parentclass extends Component{
constructor(props){
super(props);
this.state = {
editMode: false,
};
}
render(){
return(
<View>
<EditButtonClass onEditPress={() => this.editButtonPress()}/>
<View>
<Subclass editMode={this.state.editMode}/>
</View>
</View>
);
}
editButtonPress(){
this.setState({editMode: true})
}
}
Subclass:
export default class Subclass extends Component{
render(){
return(
<View>
<Finalsubclass editMode={this.props.editMode}/>
</View>
);
}
}
Final subclass:
export default class Finalsubclass extends Component{
createStyle(){
return{
opacity: this.props.editMode ? 0.5 : 1,
}
}
render(){
return(
<TouchableOpacity style={this.createStyle()}/>
);
}
}
The button:
<TouchableOpacity onPress={() => this.props.onEditPress()}>
<Image source={require('../res/edit-button.png')} style=styles.editButton}/>
</TouchableOpacity>
The editMode in props does change. If I click on one of the buttons they're getting brighter. But not directly if I enable editmode.
Whats the best way to achieve a full rerendering?
you can user react lifecycle to re-render component
https://reactjs.org/docs/react-component.html
and for above issue you can use
componentWillReceiveProps(nextProps) {
...
}
The solution was to build a View around the TouchableOpacity and applying the styles to the view.
As componentWillReceiveProps is deprecated, i would suggest using componentDidUpdate.
To answer the question in your comment, you need to check the prevProps with the new ones to not get an infinite loop.
For example:
componentDidUpdate = (prevProps) => {
if(prevProps!==this.props){ /if the previous props are different from the current props
//do what you need
}
}
As it is an object, if you need to only check a single variable you can simply do:
if (prevProps.foo !== this.props.foo) {
//do what you need
}

how to get the updated data from parent to child as props

currently, I m trying to pass props data from parent to child and it works fine, but when I m also extracting a field from asyncStorage in the constructor (let's call it brokerName) and then storing it in the props. This is where the issue arrives, the props I m getting in the child element is without brokerName.
This is the parent:
constructor(props) {
super(props);
this.getBrokername();
}
getBrokername = async () => {
const brokerName = await AsyncStorage.getItem('brokerName');
this.props = { brokerName };
console.log('brokerName', brokerName);
}
render () {
return (
<View style={{flex: 1}}>
<View style={{ flex: 1 }}>
<VehicleDetails parentProps={this.props} />
</View>
</View>
);
}
This is the child:
export default class VehicleDetails extends React.Component {
constructor(props) {
super(props);
console.log('vehicleDetails', this.props); // I m not able to get this.props.brokerName
}
}
Any kind of help would be really appreciated.
Thanks.
A few things here.
You should never mutate your props, props are read only, what you want to use in those kinds of situations is state you should read this docs section
Async actions are side effects. At this moment (react 16) you should not have any side effects in the class constructor or render method.
What you're doing doesn't work because your code is async, that means that when the component is created you dispatch a request to fetch some data, but, by the time your component renders that data is not ready to display, another problem originates from my first point, as you're mutating the props instead of using state react doesn't know that it needs to re-render and that's the root of your problem.
To fix this:
Move your async request to componentDidMount lifecycle method check the lifecycle methods here
set state when the request is data is ready
Inject the state as a prop in your child component
Parent
constructor(props) {
super(props);
this.state={
brokerName:null
}
this.getBrokername();
}
getBrokername = async () => {
const brokerName = await AsyncStorage.getItem('brokerName');
this.setState({brokerName:brokerName})
console.log('brokerName', brokerName);
}
render () {
return (
<View style={{flex: 1}}>
<View style={{ flex: 1 }}>
<VehicleDetails brokerName={this.state.brokerName}
/>
</View>
</View>
);
}
Child
export default class VehicleDetails extends React.Component {
constructor(props) {
super(props);
console.log('vehicleDetails', this.props);
}
}
you can get parents props in props key ex : <parent name={name}/> so u can access name using this.props.name
after reading Diogo Cunha's answer, Asif vora's example and Akash Salunkhe's comment above, I came up with this solution and its working fine.
Thanks for all your help.
constructor(props) {
super(props);
this.state={
brokerName:null
}
console.log('details props', this.props);
}
componentDidMount() {
this.getBrokername();
}
getBrokername = async () => {
const brokerName = await AsyncStorage.getItem('brokerName');
this.setState({brokerName:brokerName});
console.log('brokerName', this.state, brokerName);
}
render () {
return (
<View style={{flex: 1}}>
<View style={{ flex: 1 }}>
{this.state.brokerName ? <VehicleDetails parentProps={this.props} brokerName={this.state.brokerName} /> : null }
</View>
</View>
);
}
Please feel free to give your suggestion for any kind of improvement in the answer.

React Native binding functions over .map()

So I am having some trouble combining concepts of .map() and function binding. I am using .map() in the same way ngFor is used in angular, to place a custom button component on the page for every item in a user's account.
Here is some example code:
class MyButton extends Component {
constructor(props) {
super();
this.state = {
progress: 0
}
}
render() {
return(
<TouchableWithoutFeedback onPress={this.pressFunction}>
(...more code inside)
</TouchableWithoutFeedback>
)
}
pressFunction = () => {
(animate progress from 0 to 1 for some animation)
}
}
/////////////////////////////////////////////////////////////////
class Parent extends Component {
render() {
return(
{
this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton data={obj} />
</View>
)
})
}
)
}
}
So in the Parent Component, multiple MyButtons are rendered properly, each according to the passed object from the array. However, when any button is pressed, all of the pressFunctions for all MyButtons fire.
My question is I guess, how do I ensure that each pressFunction of each MyButton is bound only to the specific instance of the MyButton? I am having trouble with the scope here.
My understanding is that
functionName = () => {}
should properly bind the function to the instance, but I have tried the older ways as well with the same result.
I solved this by creating a dynamic ref on each object mapped to a MyButton, using a unique property of each obj in the array:
this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton ref={obj.name} data={obj} />
</View>
)
})
Still don't know why my it didn't bind uniquely without a ref
You should pass onPress as a props. Below is the updated code
class MyButton extends Component {
constructor(props) {
super();
this.state = {
progress: 0
}
}
render() {
return(
<TouchableWithoutFeedback onPress={this.props.onPress}>
(...more code inside)
</TouchableWithoutFeedback>
)
}
}
/////////////////////////////////////////////////////////////////
class Parent extends Component {
pressFunction = () => {
(animate progress from 0 to 1 for some animation)
}
render() {
return this.props.data.array.map(obj => {
return(
<View style={someStyle}>
<MyButton
data={obj}
onPress={this.pressFunction}
/>
</View>
)
})
}
}

How to render one React Native component after another component had rendered?

Let's say I have 2 components A and B. I want to render component A and after that render component B. To make that more obvious, I would also like to set some delay (let's say 2 seconds) between the rendering of A and B. What would be the best way to approach this? I guess I should somehow trigger the rendering of B in the componentDidMount of A, but I don't really know how to do that triggering.
Thanks :)
Your question is very vague and open to multiple implementation.
Here's my take:
export default class App extends Component {
constructor() {
super()
this.state = { displayComponentB: false }
this.displayComponentB = this.displayComponentB.bind(this)
}
displayComponentB() {
setTimeout(() => {
this.setState({ displayComponentB: true })
}, 2000) // delay
}
render() {
return (
<View style={styles.container}>
<ComponentA onComponentDidMount={this.displayComponentB}/>
{
this.state.displayComponentB &&
<ComponentB />
}
</View>
);
}
}
export class ComponentA extends Component {
componentDidMount() {
this.props.onComponentDidMount && this.props.onComponentDidMount()
}
render() {
return (
<View style={[styles.component, styles.componentA]}>
<Text style={styles.text}>Component A</Text>
</View>
);
}
}
Full code and live demo: https://snack.expo.io/SkIOLJ3eM
use setTimeout in componentDidMount.
here is a sample
constructor(props){
super(props);
this.state={
isBVisible:false
};
}
componentDidMount(){
setTimeout(() => {
this.setState({isBVisible:true});
}, 2000);
}
render(){
return(<View>
<View style={{width:100,height:100,backgroundColor:"red"}}/>
{this.state.isBVisible ? <View style={{width:100,height:100,backgroundColor:"green"}}/>:null}
</View>)
}

Appended React Native component not rerendering

I'm attempting to write a function to remove a React Native component (named "Card") from the DOM on-click, then append a new "Card" of the same class with different properties. For example, both Cards have background colors. If the first Card is green, the second Card, which should have a blue background, will inherit the green background of the original Card.
The Cards receive their background color passed as props, like so:
class Card extends Component {
constructor(props) {
super(props);
this.state = {
style: {
backgroundColor: this.props.card.backgroundColor
}
};
}
render() {
return (
<TouchableHighlight style={this.state.style}>
<Image source={this.props.card.img} />
</TouchableHighlight>
)
}
}
The main component looks like this:
class SetProject extends Component {
constructor(props) {
super(props);
this.state = {
cardArray: [{backgroundColor: 'green', img: require('~/SetProject/cardImages/ovals/1-red-empty-oval.png')}]
}
}
removeCard(){
let emptyArray = [];
this.setState({cardArray: emptyArray});
}
changeCard(){
// let emptyArray = [];
// this.setState({cardArray: emptyArray});
let newCardArray = [{backgroundColor: 'red', img: require('~/SetProject/cardImages/ovals/1-purple-shaded-oval.png')}]
this.setState({cardArray: newCardArray});
}
render() {
let cardElementArray = this.state.cardArray.map(theCard => {
return (
<Card card={theCard}></Card>
);
});
return (
<View>
<View>
{cardElementArray}
</View>
<TouchableHighlight>
<Text onPress={this.removeCard.bind(this)}>Remove Card</Text>
</TouchableHighlight>
<TouchableHighlight>
<Text onPress={this.changeCard.bind(this)}>Change Background</Text>
</TouchableHighlight>
</View>
);
}
}
So I've got two buttons: removeCard, which works great, and changeCard. If I press "Remove Card" and then press "Change Card," I see the exact results I'm looking for. The card is removed and is replaced by a new one. However, if I comment in these lines in changeCard:
// let emptyArray = [];
// this.setState({cardArray: emptyArray});
and press "Change Card" without pressing "Remove Card," the new Card has a new image but it keeps the background color of the previous Card. This also happens if I call this.removeCard() from changeCard.
In summary, I'd like to be able to perform the behavior of both of these functions simultaneously, but I'm only able to remove a Card and add a new, correctly rendered Card if I press both buttons separately.
Any ideas would be much appreciated!
Here you're using props for setting image but not setting style. You can use props as well. You have set the style in constructor. Then you want to change style but constructor is not called again but creating a new object.
You can use props setting styl as well
render() {
return (
<TouchableHighlight style={this.props.card.style}>
<Image source={this.props.card.img} />
</TouchableHighlight>
)
}
For better implementation in case properties of card gets more complex add an id property to card. You can use componentWillReceiveprops by this way unnecessary renders are neglected as well.
[{id:'1', backgroundColor: 'red', img:
require('~/SetProject/cardImages/ovals/1-purple-shaded-oval.png')}]
class Card extends Component {
constructor(props) {
super(props);
this.state = {
style: {
card: this.props.card
}
};
}
componentWillReceiveProps(nextProps){
if(nextProps.card.id != this.state.card.id)
{
setState({card:nextProps.card})
}
}
render() {
return (
<TouchableHighlight style={this.state.style}>
<Image source={this.props.card.img} />
</TouchableHighlight>
)
}
}
https://facebook.github.io/react/docs/react-component.html#componentwillreceiveprops
Don't you get warning about missing keys in array? Use unique identifier (or it's index in array as last resort) for each card, and use it to set key prop on each item in array. This way, when card in array changes, react can re-render it, because it's a new card to it.
let cardElementArray = this.state.cardArray.map(theCard => {
return (
<Card key={theCard.id} card={theCard}></Card>
);
});
Read more about keys here in React docs.

Resources