I have a requirement of showing a tab like behavior inside a react native Modal
I have started by creating a state variable selectedSub which is initialized with value 'from'
Now my modal has 2 TouchableHighlight as below
_availabiltyModal() {
return (
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'from' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'from' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'from' && styles.activeText}>From</RkText>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'to' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'to' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'to' && styles.activeText}>To</RkText>
</TouchableHighlight>
{this._renderPicker()}
)}
These two are responsible to change the state param selectedSub as required.
Based on this state param i am conditionally showing another component i made and imported as below
_renderPicker() {
if (this.state.selectedSub == 'from') {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.from }} />
} else {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.to }} />
}
}
I have called this function in the Modal below the TouchableHighlight's
Now as per the RN docs when ever the state variable is updated with this.setState() method the component should re-render. Everything else is working fine (TouchableHighlight styles changing) and also the updates to the state are reflecting in the console. But the _renderPicker function does not return the changed view and is always stuck on the view which was set when the parent was initialized as pointed earlier.
Could somebody help me point the problem here which i might have been overlooking. Also on the side note all this calls are actually directly made outside the main render() method. Could that be a possible issue
------update------
here is the complete reference
render() {
return({this._availabiltyModal()}
<View style={appStyles.tagsWrapper}>
{this.state.week.map((day, i) => {
return (
<TouchableHighlight
key={i}
activeOpacity={0.9}
style={[appStyle.mr10, appStyle.mb10]}
onPress={() => {
this.setModalVisible(true, day);
}}>
<Text style={appStyle.tag}>{day}</Text>
</TouchableHighlight>
)
})}
</View>
)
}
Move the _renderPicker method inside the render() method like...
render() {
...
{this._renderPicker()}
...
}
Looking at the code of the MODAL component from react-native
render(): React.Node {
....
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
this.props.children
);
return (
<RCTModalHostView
....>
<View style={[styles.container, containerStyles]}>
{innerChildren}
</View>
</RCTModalHostView>
);
}
The state you are changing are of the component that use the modal component thats render its children through the upper function.. when the state update it only re render the component whose state is updated.. so somewhere down rendering child inside component it does not get re render until force re render is applied.
Here is an helpful article to further explain how exactly this functions, forcefully re-rendering the child component
Related
Note: The component prop accepts component, not a render function. Don't pass an inline function (e.g. component={() => }), or your component will unmount and remount losing all state when the parent component re-renders. See Passing additional props for alternatives.
This warning from the simulator.
enter image description here
function HomeScreen(props: Object) {
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={() => {
return (
<View>
<Text>Example Text</Text>
</View>
);
}}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
It's warning you that component is going to be re-rendered every time screen is re-rendered. This is because every time the component arg is evaluated it returns a fresh inline function. (the prop changes every time thus it must always re-render)
Evaluation of the same inline function twice yields different objects.
Eg.
let funcs = []
for (let i =0 ; i<2; i++) {
funcs.push(() => 1)
}
funcs[0] == funcs[1]
>>> false
You can wrap this is useCallback to get a stable function that only updates when it needs to (never in your case since it captures no state). More on hooks here
function HomeScreen(props: Object) {
let component = useCallback(() => {
return (
<View>
<Text>Example Text</Text>
</View>
);
}, [] // no dependencies. This lambda will only be evaluated once.
);
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={component}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
Or since this function doesn't depend on anything stateful you could also just declare it globally at the top of this file.
let component = () => {
return (
<View>
<Text>Example Text</Text>
</View>
);
};
function HomeScreen(props: Object) {
return (
<Navigator initialRouteName="Empty1">
<Screen
name="Empty1"
component={component}
/>
<Screen name="Home1" component={HomeScreen1} />
</Navigator>
);
}
You need to understand what is hiding behind JSX syntax.
When you use <SomeComponent/> it is added to the React Virtual DOM and exists there until the tree is changing and component has to be unmounted. It survives rerenders, the DOM is updating, but component lives.
When you use () => <SomeComponent /> this component is a new object on every rerender of the parent. If it has state (via setState hook) it will be reinitialized. If it has some code in useEffect(() => {}, []) hook it will be called again.
On pressing the button(ButtonComponent), the state of the button gets changed. Now I'm passing that state to the <FlatListItem> (a child component ) inside <FlatList>. Depending on that status, the each item in the <FlatList> should be re-arranged.
I just got to know about this extraData prop but not sure how to make use of it in the code.
It's not something new but it is like simple checkbox implementation.
Everything is working fine but when I press the select all button all the remaining select buttons are not getting toggled to selected.
class FlatListItem extends Component{
constructor(props){
super(props)
const{ isSelected }=this.props
this.state={
selectedStatus:isSelected,
}
}
changeSelectStatus=(key)=>{
this.setState({selectedStatus:!this.state.selectedStatus});
return key;
}
render(){
return(
<View style={{flex:1,
flexDirection:'row',
backgroundColor:'white'}}>
<View>
<Image
source={{uri:this.props.item.imageUri}}
style={{width:50, height:50, margin:5}}>
</Image>
</View>
<View>
<Text style={{color:'black', padding:10, fontSize:16}}>{this.props.item.name}</Text>
</View>
<View style={{flex:1, alignItems:'flex-end', paddingRight:-10}}>
{this.state.selectedStatus?
**<ButtonComponent buttonColor={"black"} buttonTextColor={"white"} fullRounded={true}
borderHighlight={true} buttonWidth={70} buttonHeight={30}
onPress={()=>this.props.showSelected(this.changeSelectStatus(this.props.item.key)) }>
Selected
</ButtonComponent>
:
<ButtonComponent buttonColor={"white"} buttonTextColor={"black"} fullRounded={true}
borderHighlight={true} buttonWidth={70} buttonHeight={30}
onPress={()=>this.props.showSelected(this.changeSelectStatus(this.props.item.key)) }>
Select
</ButtonComponent>
}
</View>
</View>
)
}
}
export default class SelectMembersBody extends Component {
constructor(props){
super(props)
this.state={
selectedButtons:[],
selectAllBtnStatus:false,
}
}
selectAllMembers=()=>{
let allMembers=[];
if(!this.state.selectAllBtnStatus){
membersData.forEach(element => {
if(!this.state.selectedButtons.includes(element.key))
allMembers.push(element.key)
});
this.setState({
selectAllBtnStatus:!this.state.selectAllBtnStatus,
selectedButtons:[...this.state.selectedButtons, allMembers]
})
}
else{
this.setState({
selectAllBtnStatus:!this.state.selectAllBtnStatus,
selectedButtons:[...allMembers]
})
}
}
showSelected=(callback)=>{
let val = callback;
if(!this.state.selectedButtons.includes(val))
this.setState({selectedButtons:[...this.state.selectedButtons, val]});
else{
let newMarkers=[...this.state.selectedButtons]
let index = newMarkers.indexOf(val);
if (index >= 0) {
newMarkers.splice( index, 1 );
}
this.setState({selectedButtons:newMarkers});
}
}
render(){
return(
<View style={{flex:1, }}>
<Text>{this.state.selectedButtons}</Text>
<View>
{this.state.selectAllBtnStatus?
<ButtonComponent buttonColor={"black"} buttonTextColor={"white"} fullRounded={true}
borderHighlight={true} buttonWidth={85} buttonHeight={30} onPress={this.selectAllMembers}>
Selected All
</ButtonComponent>
:
<ButtonComponent buttonColor={"white"} buttonTextColor={"black"} fullRounded={true}
borderHighlight={true} buttonWidth={85} buttonHeight={30} onPress={this.selectAllMembers}>
Select All
</ButtonComponent> }
</View>
<FlatList data={membersData} extraData={this.state}
renderItem={({item, index})=>{
return(
<View>
<FlatListItem item={item} index={index} isSelected={this.state.selectAllBtnStatus} showSelected={this.showSelected} ></FlatListItem>
</View>
)
}
}></FlatList>
</View>
)
}
}
I know that it's quite harder to step into someone's shoes. The code I provided in my question might not be that effective since I'm a learner. That's why it is uncomfortable to get on to the code flow. So, I decided to answer my own question after reading some articles, docs and similar questions on StackOverFlow.
So, my question is why the flatlist was not getting re-rendered on setState. To be more precise, if you've gone through the pictures above, on selecting the Select All button all the buttons in the items should be toggled to selected state.
To make the flatlist re-render, we need to add an additional prop "extraData"
By passing extraData={this.state} to FlatList we make sure FlatList itself will re-render when the state.selected changes. Without setting this prop, FlatList would not know it needs to re-render any items because it is also a PureComponent and the prop comparison will not show any changes.
You can get it's full documentation here.
In my case, I set extraData = {this.state} since the status of buttons in each of the flatlist item depends on the array which is present in the parent component.
So, I've sent it to child component via props.
Now the flatlist is working fine and re-rendering on every state update.
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.
I have the following scenario.
function MyComponent() {
return (
<View>
<TextInput ref={ref => (this.input = ref)} style={styles.input} />
{this.input.isFocused() && <Text>Hello World</Text>}
</View>
);
}
This actually works fine, but I get the warning:
MyComponent is accessing findNodeHandle inside its render. render
should be a pure function.
How do I conditionally render The text block and avoid this warning?
You can use component state :
class MyComponent extends React.Component {
state = { isFocused: false }
handleInputFocus = () => this.setState({ isFocused: true })
handleInputBlur = () => this.setState({ isFocused: false })
render() {
const { isFocused } = this.state
return (
<View>
<TextInput
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
/>
{isFocused && <Text>Hello World</Text>}
</View>
)
}
}
You cannot reference refs from the render() method. Read more about the cautions of working with refs here.
As you can see in the image below, the first time the component mounts, refs is undefined, when I change the text (Which changes the State, which re-renders the component) refs is now available.
An optimal solution would be using state. I was going to post a solution but Freez already did. However, I am still posting this so you know why you should be using state instead of refs.
Is there any chance to use a component as a global ActivityIndicator which has transparent color and had been created by me on React-Native?
Details:
I use a redux store to update the UI. So I intend to show an ActivityIndicator by updating the store.
I've created an ActivityIndicator component with name ActIndicator.
I have a main App component that contains the app.
I have a Root component that wraps the ActIndicator and App components.
The ultimate code of render method of Root component looks like the following:
render() {
if (this.state.showActivityIndicator) {
return(
<ActIndicator>
<App />
</ActIndicator>
)
}
return (</App>)
}
I've tried several methods but I can not be successful.
I guess my brain is stopped.
I also guess there may be a logic mistake.
const withLoadingOverlay = (Component) => class withLoadingOverlayView extends React.Component { props: withLoadingOverlayProps
// Indicator view styles loadingOverlay = (style) => (
<View style={[style, styles.someDefaultStyles]}>
<ActivityIndicator color={someColor} size="large" />
</View> )
render() {
const { pending, ...passProps } = this.props;
const { width, height } = Dimensions.get('window');
return (
<View style={{ flex: 1 }}>
<Component {...passProps} />
{pending && this.loadingOverlay({ width, height })}
</View>
); } };
I used to wrap whole container with HOC and with redux action to set on start pending prop true and on success or fail to set on false so this prop will be consumed by HOC and indicator will be displayed only when pending is set on true.
In container you have to wrap component in connect
export default connect(
(state) => ({
pending: state.reducer.pending, // pending prop should be here
}),
(dispatch) => ({ dispatching redux actions })
)(withLoadingOverlay(WrappedComponent));
I don't think you're supposed to pass App as a child, the way I use it is more like this:
render() {
if (this.state.showActivityIndicator) {
return(
<View>
<ActivityIndicator />
<App />
</View>
)
}
return (<App />)
}
but it would probably be better to set it up like this:
render() {
return (
<View>
<ActivityIndicator animating={this.state.showActivityIndicator} />
<App />
</View>
)
}