React-native child component not updating when parent state updates - react-native

I have a parent component which holds a child component. The parent is getting its data from redux. The object in store looks something like the below:
//object in redux store
object :[{
item: {
name: 'item'1,
selected: false
},
item:{
name: 'item'1,
selected: false
}
}]
My Parent component contains a FlatList which renders the child component:
//parent component
<FlatList
data={this.props.object}
renderItem={( object ) => <Object object={object.item} />}
keyExtractor={object => object.id}
/>
My child component contains a button, which toggles the selected property of each item. The desired behavior is that the style of the button changes based on the value if the selected property. My child button component looks something like the below:
// child component
render(){
return (
<View>
<Text>{this.props.name}</Text>
<Button
title="toggle"
buttonStyle={{backgroundColor: this.props.selected? 'red' : 'green'}}
onPress={() => handling toggle by changing state in store, it works fine and
console.logs correctly}/>
</View>
);
}
After the button is toggled, the state of the object does change, and console.logs correctly, however the style of the button only updated when i go out and back into the page as if the child component is not updating.
How can i get the button style to update directly on button toggle? Thanks

More information would help, but my initial thoughts are that the toggled state might not be causing the FlatList component to pick up the changes, in order to trigger a re-render.
This can be resolved by the optional extraData prop that can be passed into your FlatList component
https://reactnative.dev/docs/flatlist#extradata

it turns out the changes I was doing in my reducer did not trigger a component update due to the shallow equality comparison redux performs.
I fixed by returning this in my reducers
let obj = {...}
return { ...state, object: {...state.object}
instead of
let obj = {...}
return {...state, object: obj }

Related

How can we create a react-native component which can be displayed with .show() method

I am looking for pointers where I can start from.
I want to create a react native feedback form which can be displayed with .show method.
for eg:
export class FeedbackComponent extends React.component{
show() {
// define this method in a way so that can be called from outside as FeedbackComponent.show()
// which eventually create a new screen with below rendered View
}
render (){
return <View>Feedback Form</View>
}
}
I should be able to use this Component in any other component as
import FeedbackComponent from './FeedbackComponent'
new FeedbackComponent.show()
I would always start with considering the application state. UI in React is updated whenever the State of our components changes.
In your case I would have to think of the parent context in which your feedback form will need to be displayed. In its simplest form, this context will likely be a parent screen-component within which your FeedbackForm component is either shown or hidden.
I've made a Snack of a simple implementation you can find it here: https://snack.expo.io/#stephos_/show-feedback-form
In my case, the App component is the parent screen-component within which we need to render or not render a FeedbackForm Component.
So I would start with adding the relevant state property to the App (parent screen) component like so:
state = {
feedbackFormVisible : false
}
I would then define a method within the same parent class in order to toggle the state when we need to like so:
handleFeedbackFormVisibility = () => this.setState(prevState => ({feedbackFormVisible:!prevState.feedbackFormVisible}))
This handler takes in the previous state in our parent component and toggles the value of the feedbackFormVisible property (i.e. from false to true).
In my case, I call this handler every time we press a Button component like so:
<Button title="Give Feedback" onPress={this.handleFeedbackFormVisibility}/>
You could however trigger the same handler and update the state of the parent component in any other way (i.e. after a timer expires or after a specific scroll point is passed).
The App Component's render method will then decide if the FeedbackForm component should be displayed based on the value of the feedbackFormVisible in our App Component's state. We achieve this by wrapping our FeedbackForm component within an Elvis Conditional within the render method which will return the the appropriate UI (i.e. either with a visible feedback form or not):
{ this.state.feedbackFormVisible ? () : null}
Below the full App component code:
import * as React from 'react';
import { Text, View, StyleSheet, Button } from 'react-native';
import FeedbackForm from './components/FeedbackForm';
export default class App extends React.Component {
state = {
feedbackFormVisible : false
}
handleFeedbackFormVisibility = () => this.setState(prevState => ({feedbackFormVisible:!prevState.feedbackFormVisible}))
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>
This is the App Parent Component
</Text>
{
this.state.feedbackFormVisible ?
(<FeedbackForm />)
: null
}
<Button title="Give Feedback" onPress={this.handleFeedbackFormVisibility}/>
</View>
);
}
}
And below the Feedback Component code (Notice that the shown/hidden logic is actually handled in the parent component not in here):
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
export default class FeedbackForm extends React.Component {
render() {
return (
<View style={styles.container}>
<Text style={styles.paragraph}>
This is the feedback form!
</Text>
</View>
);
}
}

How to stop TextInput losing focus onChangeText?

I want to change the state of a parent component from a child's TextInput.
The problem is every time it changes the parent state the component re-renders, the TextInput is blurred and the keyboard disappears.
I tried keeping all the logic in the some component and changing the parent state without passing props. Now I've tried extracting the InputText container and putting it into a new file, changing the parents' state and receiving the value through the props, that's not working either.
Here is the text field component:
export default (ProfileTextInput = ({
placeholder,
label,
handleChange,
name,
value
}) => {
return (
<View style={styles.inputComponent}>
<Text style={styles.labelText}>{label.toUpperCase()}</Text>
<TextInput
key={Math.random()}
placeholder={placeholder || ""}
value={value}
onChangeText={val => handleChange(val, name)}
/>
</View>
);
});
and this is how it's being used:
const [newUserData, setNewUserData] = useState({ ...userData });
const changeHandler = (value, name) => {
setNewUserData({ ...newUserData, [name]: value });
};
return(
<ProfileTextInput
label="Username"
defaultValue={newUserData.username}
name="username"
value={newUserData.username}
handleChange={changeHandler}
/>
)
I expected it to continue letting me type like a normal TextInput, but it's only typing one letter and losing focus.
Try removing the defaultValue props from your components.
defaultValue is just the initial value passed to an uncontrolled component. Since you're setting the input value with a change handler, that makes your input a controlled component, and so you should just set the value explicitly.
Read these articles for more details on the differences between these cases:
React forms and controlled components
Uncontrolled components

React Native ios Switch in FlatList not toggling after value changed

I am trying to toggle ios Switch in react native. But the switch comes back to initial position as soon as I change it.
What I have:
class ABC extends Component {
constructor(props) {
super(props)
this.state = {
obj: []
}
}
fetch(){
// fetch something from remote server, set it to state object array
}
setStatus(id, value){
var temp = [...this.state.obj]
temp.map((t) => {
if (t.id == id) {
t.flag = value
}
})
this.setState({ obj: temp })
}
render() {
return (
<View>
<FlatList
data={this.state.obj}
renderItem={({ item }) =>
<View>
<Text>{item.name}</Text>
<Switch
onValueChange={(val) => this.setStatus(item.id, val)}
value={item.flag}
/>
</View>
}
keyExtractor={({ id }, index) => id.toString()}
/>
</View>
);
}
}
I logged the before and after value of obj state and they seem to update. Should the FlatList be rendered again (like a web page refresh) ? Or is there something I am missing ? Searched SO for answers, couldn't find my mistake.
Flatlist has a prop called extraData.
This prop tells Flatlist whether to re-render or not.
If data in extraData changes then flatlist re-renders based on new data provided in data prop.
So whenever you need to re-render flatlist just change something in extraData.
Best way is to pass state toextraData which is passed to Data.
So, just pass extraData={this.state.obj}.
there also other way called forceUpdate.
you can call this.forceUpdate().
but this is not recommended because this will render not only flatlist but entire component in which you are calling this.

How change a variable of a parent in react-native

I want to change client = {state:0}
You can access it by using this.client.state
I also have a child which contains a button.
I'm trying to change this 'state' variable when you press the button.
For some reason everything I find everything on the internet not working for me.
I've been stuck at it for 5 hours and I think it's time to ask help myself
import React from 'react';
import { StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native';
import Home from './Components/Home';
import Activity from './Components/Activity';
export default class App extends React.Component {
client = {state:0}
render() {
if(this.client.state == 0){
return(
<View style={{flex: 1}}>
<Home />
<Child />
</View>
);
} else {
return(
<View style={{flex: 1}}>
<Activity />
<Child />
</View>
);
}
There are different ways of doing this. It could be done with Redux for example, but let's take a simpler approach.
Also note that it can't be done by props, because a child component cannot update its parents' props.
Also note that the way you are using the state seems rather strange. It should be set on the class level (or component level).
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {myProperty: 0};
}
}
You could pass a callback method to the Child React Component.
<Child callback={this.onButtonClick} />
On the Client Component, create that callback method:
onButtonClick() {
this.setState({buttonClicked: true});
}
To keep it clean, define the initial value in the constructor. You'll also have to bind the function to have a correct this parameter, otherwise the this variable will be from the event instead of the class you're expecting.
constructor(props) {
super(props);
this.state = {buttonClicked: false};
this.onButtonClick = this.onButtonClick.bind(this);
}
That's it for the Client component.
Now on the Child Component, you'll need to trigger this callback method when possible.
Imagine the Child has the following button, add an event handler on the child component as well, onChildButtonClick. You'll also have to bind in the constructor.
constructor(props) {
super(props);
// bind this for access to this class' data
this.onChildButtonClick = this.onChildButtonClick.bind(this);
}
onChildButtonClick() {
// Might want to do a typeof is function check here too
if (this.props.callback) {
// Trigger the callback on the parent component, letting it know the button was triggered
this.props.callback();
}
}
render() {
return <button onClick={this.onChildButtonClick}>Click me</button>;
}
During initialisation, the Parent component sends a callback method to the child component. Whenever the button is clicked on the child component, the child component triggers the function (callback) given by the parent, essentially running a piece of code on the parent component, which then updates the state with the requested value (could be a string, or anything).
Redux
Redux is another way of doing it, which basically keeps a sort of tracked database that can be used from any component, by pageload - however, that would require an entire tutorial.

setNativeProps on Button Component

When I define a button in React Native as:
<Button ref="buttonRef" title="Button" onPress={this.buttonPressed}/>
And its onPress function as:
buttonPressed(){
this.refs.buttonRef.setNativeProps({color:"#ffffff"});
}
I get the following error:
this.refs.buttonRef.setNativeProps is not a function. (In
'this.refs.buttonRef.setNativeProps({
color: "#ffffff"
})', 'this.refs.buttonRef.setNativeProps' is undefined)
However, if I were to define any other type of component, e.g. text input as
<TextInput ref="userInputRef" value={"this is text"} />
With the function changing its props:
buttonPressed(){
this.refs.textRef.setNativeProps({color:"#ffffff"});
}
Everything changes correctly.
Is there a reason that the button component is unable to have its native props set through setNativeProps?
Button component is a simple custom component created from Touchable components and it doesn't have a ref property. You can check the source code of Button component here.
If you need to change properties of a component you should use state values for that.
Sample
buttonPressed = () => {
this.setState({color:"#ffffff"});
}
//....
<Button ref="buttonRef" color={this.state.color} title="Button" onPress={this.buttonPressed}/>