React Native Component not re-rendering despite render function running - reactjs

I am a little baffled here ..
I have some code like this
renderTopic(topic){
if(topic.isMine){console.log('work?')}
return (
<View key={topic.id.toString()} >
<TouchableOpacity onPress={() => this.addToic.call(this, topic)} >
{topic.isMine && <Topic topic={topic} active={true}/>}
{!topic.isMine && <Topic topic={topic} active={false}/>}
</TouchableOpacity>
</View>
)
}
render() {
console.log("re-rendering", this.props.myTopics)
return (
<View style={s.welcome}>
<ScrollView>
<View style={s.topics}>
{this.props.me.myTopics.map(topic => this.renderTopic(topic) )}
</View>
</ScrollView>
</View>
)
}
The problem is when I update topics using the traditional redux -> 'dispatch action' method, the re-render function fires, the should component update function fires, everything looks like it works but the topic.isMine conditional rendering never updates and switches the components over.
The correct result is displayed if I change page, the app then re-renders correctly.

Probably your reducer does not create new topic object. React sees, that reference to new topic matches old topic and skips calling renderTopic(topic) with same reference value as before.

Related

Issue on Hide/Show component in React Native

I am new in React native development and i am trying to do something simple. I have 4 components <TouchableOpacity> in a React Class extending Component.
In the render function, i am trying to hide three of these components while i am pressing on one.
render(){
[...]
return (
<TouchableOpacity style={styles.filterButtonAll} onPress={()=>this.toggleStatus()}>
<Ionicons size={15} name='XXX'> Show/Hide</Ionicons>
</TouchableOpacity>
<TouchableOpacity style={styles.filterButton}>
<Ionicons size={15} name='XXX'> Text1</Ionicons>
</TouchableOpacity>
<TouchableOpacity style={styles.filterButton}>
<Ionicons size={15} name='XXX'> Text2</Ionicons>
</TouchableOpacity>
<TouchableOpacity style={styles.filterButton}>
<Ionicons size={15} name='XXX'> Text3</Ionicons>
</TouchableOpacity>
[...]
)
}
toggleStatus function :
toggleStatus(){
this.state.showMenu = !this.state.showMenu;
};
And i have my showMenu in my state.
I tried conditional, and many other things following many posts about this subject, but when i am logging {this.state.showMenu} , it is dynamic in my toggleStatus() function but it's always the same value in my render function.
I think i am doing something wrong, thanks in advance for your help :)
The way your updating your state is incorrect. You have to call a function called setState() and then pass the updated state.
Reference for setState: https://reactjs.org/docs/react-component.html#setstate.
Example:
toggleStatus(){
this.setState({showMenu : !this.state.showMenu}); // RIGHT
// this.state.showMenu = !this.state.showMenu; // WRONG
};
Recommend way:
basically when your state update depend upon the previous state its better to use this approach.
toggleStatus(){
this.setState((state, props) => {
return {showMenu: !state.showMenu};
});
// this.state.showMenu = !this.state.showMenu; // WRONG
};

disable touchableOpacity in React native

I have develop an barcode scanner apps, the first module is Scan and get the data from the barcode and it is compulsory to do this if the user wanted to process with another module so I plan to use disabled attribute in touchableOpacity. Home screen will show the data of the user scan by using route.params && route.params.variableNames
example:
So i wish to implement a function that will disable the second module on the home screen which is Start Job if the user did not scan the barcode on login module
code that i have tried
export default function Home ({route}){
const navigation = useNavigation()
const [status,setstatus]=useState(true)
useEffect(()=>{
{route.params && route.params.userid !==''? setstatus(false):setstatus(true)}
},[]);
return(
<View style={styles.container1}>
<Text style={styles.design1} >UserID</Text>
<TextInput
style={styles.design}
placeholder={route.params && route.params.userid}
editable={false}
/>
<TouchableOpacity onPress={()=> navigation.navigate('Scanlogin')} style={styles.design2} >
<Text style={{color:'darkslateblue',fontSize:20,fontWeight:'bold',textAlign:'center'}}>LOGIN</Text>
</TouchableOpacity>
<Text style={styles.design6}>SCAN YOUR BADGE BEFORE YOU PROCEED TO START JOB</Text>
<TouchableOpacity disabled={status} onPress={()=> navigation.navigate('Start',{userid:route.params && route.params.userid})} style={styles.design4}>
<Text style={{color:'darkslateblue',fontSize:20,fontWeight:'bold',textAlign:'center'}}>START JOB</Text>
<StatusBar style="auto"/>
</View>
);
}
the start job get disable all the time when i implemented the attribute disabled. hope u guys helps. Thanks
If the useEffect factor is set to empty, the function inside only triggered on mount.
You can set variable(s) as the factor(s), so the useEffect will listen on that variable, route.params.userid, and the mount of the element.
useEffect(()=>{
{route.params && route.params.userid !==''? setstatus(false):setstatus(true)}
},[route.params.userid]);
Extra: I don't have the complete code, you may change the factor as keeping the above concept in mind

onPress of TouchableOpacity gets called when loading Flatlist

I have a flatlist with a search function. In the next step, I want something to happen when I press within the item. I'm facing the problem that the TouchableOpacity is visible, but nothing happens when it's pressed. I've tried the same with a Button and face the same issue.
Instead of onPress being called when something is pressed, it somehow get's called once the screen loads immediately. If I for example console.log(item.title) I get all the titles in my console log that are currently in the flatlist.
I've tried to figure out what is causing this for many hours, but can't seem to find any reason. Any help is highly appreciated.
I have a Flatlist that is populated with data, set up as follows:
return (
<View style={styles.list}>
<FlatList
data = {this.state.data}
renderItem={renderListItem}
keyExtractor={item => item.id}
ListHeaderComponent={this.renderHeader}
/>
</View>
);
}
}
const renderListItem = (item) => {
return (
<MyTouchable
id={item.item.id}
title={item.item.title}
/>
);
}
MyTouchable Component looks like this:
<View>
<TouchableOpacity onPress={console.log("Pressed")}>
<View style={styles.mainView}>
<View>
<Text style={styles.text}>{props.id} {props.title}</Text>
</View>
</View>
</TouchableOpacity>
</View>
Try to pass the console.log inside an arrow function
<TouchableOpacity onPress={() => console.log("Pressed")}>
...
</TouchableOpacity>
You can’t write like this: <Component makeThing={myFunc()} />. You should write like that: <Component makeThing={() => myFunc()} /> if you are not using useCallback.
Make sure to read the docs about passing functions to components.
You must provide and arrow function, and call your function inside it.
Or just use useCallback hook:
const Component = () => {
const myFunction = useCallback(() => {
// do something
}, []);
return <AnotherComponent coolFunction={myFunction} />
}

ScrollView positions being restarted on a child component re-render (update)

I am having an issue (this is probably something I am ignoring within the render cycle) maintaining the position of two different scroll views when some of its child components are updated.
I have a hierarchy of views that is like:
<ScrollView vertical scrolling ability>
...
<ScrollView horizontal and vertical scrolling ability>
...
<Matrix>
<Cell updateCellStatusHandler={handler}>
</Matrix>
...
</ScrollView>
</ScrollView>
So, the updates on the internal cells, are resetting both scrolls on cell status update and this generates a super weird experience with the user having to scroll down/left/right back to continue interacting with the Matrix of cells with a status I have.
I have tried to save the scrollOffset (x,y) using useState but if I change some cell, the state is reseted to (0,0) which is my initial state.
const [scrollOffset, setScrollOffset] = useState({
scrollX: 0,
scrollY: 0,
})
But without luck.
<ScrollView
{...props}
scrollEventThrottle={16}
ref={scrollReference}
// tslint:disable-next-line: jsx-no-lambda
onScroll={event => {
setScrollOffset({
scrollX: event.nativeEvent.contentOffset.x,
scrollY: event.nativeEvent.contentOffset.y,
})
}}
onScrollEndDrag={event => {
console.log(event.nativeEvent.contentOffset.y)
console.log(event.nativeEvent.contentOffset.x)
}}
>
{props.children}
</ScrollView>
One possible approach to solve this is to have a mechanism that allow me to save the scroll position before the update. But this will complicate a lot the communication between components, etc.
By the way, the cell status update is being handled via Redux.
If some of you can bring you some light over this, would be great.
---- UPDATE 1 (Code of the panel component added) ----
Parent component is:
<View style={styles.container}>
<CustomScrollView enableResetScrollToCoords={false}>
<Section>
<WhiteContainer>
<View style={styles.rateContainer}>
<DescriptionTextStatus
statusText={getMessageForRate(availabilityRate)}
descriptionText={getDescriptionForRate(
availabilityRate,
isTeacher,
)}
icon={getImageForRate(availabilityRate)}
/>
</View>
</WhiteContainer>
</Section>
{!loggedUserIsTeacher() && <AvailabilityStart />}
<AvailabilityPanel />
<AvailabilityStatus />
<AvailabilityButtonSave />
</CustomScrollView>
</View>
Availability Panel is one of the childs
export const AvailabilityPanel: React.FunctionComponent<{}> = () => {
const panel: Cell[][] = useSelector((state: ReduxStore) => {
return get(state.entities, 'availability.panel', undefined)
})
if (panel === undefined) {
return <Nothing />
}
return (
<Section>
<WhiteContainer>
<LinearGradient
start={{ x: 0, y: 0 }}
end={{ x: 1, y: 0 }}
colors={[Palette.White, Palette.White68]}
>
<View style={styles.rateContainer}>
<DescriptionTextStatus
statusText={strings.warning}
descriptionText={strings.selectionMessage}
icon={'clock'}
/>
<View style={styles.separator} />
</View>
<View style={styles.container}>
<ScrollView
style={styles.scrollView}
directionalLockEnabled={false}
horizontal={true}
showsHorizontalScrollIndicator={false}
showsVerticalScrollIndicator={false}
>
<View style={styles.contentContainer}>
<View style={styles.weekdaysContainer}>
<PanelHeader weekdays={weekdays} />
</View>
<View style={styles.rowsContainer}>
{hours.map((hourLabel: string, index: number) => {
return (
<PanelRow
key={index}
hourLabel={hourLabel}
hoursRow={panel[index]}
rowIndex={index}
/>
)
})}
</View>
</View>
</ScrollView>
</View>
</LinearGradient>
</WhiteContainer>
</Section>
)
}
Thanks in advance.
ScrollView does not reset scroll position because of child updates, regardless of whether you use Redux or not. You don't need to try to patch it with workarounds like saving current scroll positions. Rather just figure out what's wrong with your setup
It's hard to tell because you didn't provide your actual code, but I'm pretty sure one or both ScrollViews are remounted on updates (old ScrollView is removed and new one is created in its place with its own scroll position). It's likely that you are declaring new components inside render function so React will just remount them every time
I think the overall idea of saving the scroll position of your scrollview is good.
I think the fact that your state is reset to 0 is probably due to Redux (not sure however, could you precise how your component is connected to redux ?). I am not so familiar with Hooks, but in a React Class component, I would try to save the scroll position in a non state property of the class. Maybe save it in a variable outside of your functional component ?
Then in the handler to manage cell updates, you could ensure that your scrollview scrolls to the position you saved (using ScrollView.scrollTo(), with {animated: false} option so that the animation is not visible for the user)

How can I create a modal and render it on the fly?

I am building my first app in React Native, I have a ListView that displays a number of items and each one is clickable. My question is when I click on a row I would like a modal to appear above. Similar in IOS to clicking and the adding a subview.
renderRow( rowData ){
return (
<TouchableHighlight onPress={() => this.pressRow()}>
<View style={styles.parcelInfoContainer}>
</View>
</TouchableHighlight>
);
}
Now the problem is I am unsure about what to do in the function called by pressRow in relation to rendering the new component. In IOS I would simply add the view at this point but I am not sure how to go about it in React Native. I understand the idea of components just not how to render a new component on the fly.
If you're going to render the same Modal, in terms of layout, you should define it in render() function. Rendering on the fly is not possible, but you can change the states to make modal show what you like.
onPressRow = (dataFromRow) => {
this.setState({
modalText: dataFromRow.text
})
this.showModal(); //Call your function to show modal
}
render() {
return (
<View>
<FlatList />
<Modal>
<View>
...
<Text>{this.state.modalText}</Text>
</View>
</Modal>
</View>
)
}
However, if you want different layout for each row, you should write a this.getModalContent() like function to determine which layout should be rendered. You should call this function in render => <Modal> {this.getModalContent()} </Modal>
getModalContent = () => {
switch (this.state.modalText) { // define a better logic here.
case 'a':
return ( <Text> hey </Text>)
...
}
}

Resources