My entire goal was to navigate from a screen while changing states in the screen I am navigating to. I have successfully done that in a minimal working example, however in my overall project, the screen I am navigating to needs to be passed the state through a couple levels.
I have two examples. In the first example(You must run the examples in IOS or android, you can see what I need to achieve, everything works as it should. You can move from screen3 to the home page and the states change along with the slider button moving.
In the second example, you can see right off the bat I have an error due to my attempt at passing states the same way I do in the original example however there is one more level I need to pass through in this example. You can see by removing line 39 in this demo, it removes the error so obviously I am not passing states correctly. I need to pass states from Home to Top3 to Slider
Here is example 1 and here is example 2 while I have also provided some code below that highlights the differences where the error occurs in the two examples.
Any insight at all is appreciated more than you know! Thank you.
Example1 -> you can see I directly render the slider button which causes zero issues.
const Home = ({ route }) => {
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Slider
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'|| false}
/>
</View>
</View>
);
}`
Example2 -> You can see I render Slider in a file called Top3, I am struggling to pass these states from Home to Top3 to Slider.
const [isVisile, setIsVisible] = React.useState(true);
const [whichComponentToShow, setComponentToShow] = React.useState("Screen1");
React.useEffect(() => {
if(route.params && route.params.componentToShow) {
setComponentToShow(route.params.componentToShow);
goToMap()
}
}, [route.params]);
const goToMap = () => {
setComponentToShow("Screen2");
}
const goToList = () => {
setComponentToShow("Screen1");
}
return(
<View style={{backgroundColor: '#d1cfcf' ,flex: 1}}>
{whichComponentToShow === 'Screen1' && <ListHome />}
{whichComponentToShow === 'Screen2' && <MapHome />}
<View style={{position: 'absolute', top: 0, left: 0, right: 1}}>
<Top3
renderMap={goToMap}
renderList={goToList}
active={route.params && route.params.componentToShow==='Screen2'|| false}
/>
</View>
</View>
);
}
Top3
export default class Top3 extends React.Component {
goToMap = () => {
this.props.renderMap();
};
goToList = () => {
this.props.renderList();
};
render() {
return (
<View>
<Slider renderMap={this.goToMap.bind(this)}
renderList={this.goToList.bind(this)}
active={active}/>
</View>
);
}
}
from your examples, I think you are not extracting active from props properly.
here is the demo working code your example2 code https://snack.expo.dev/4atEkpGVo
here is the sample code for component Top3
export default class Top3 extends React.Component {
goToMap = () => {
this.props.renderMap();
};
goToList = () => {
this.props.renderList();
};
render() {
const {active=false} = this.props;
return (
<View>
<Slider renderMap={this.goToMap.bind(this)}
renderList={this.goToList.bind(this)}
active={active}/>
</View>
);
}
}
if you want to share states between multiple screens, then you might want to use global stores like react context api or redux instead of passing states to each screen that would be simple
Related
I have a react function component, that I do not want to convert to a class component. I want it to have the functionality so that when it is pressed, the image is changed. However, currently, it is only showing one of the images (checked.png). What am I doing wrong?
const SignUpScreen = () => {
const [check, setCheck] = useState(false);
const changeImage = () => {
setCheck((prevCheck) => !prevCheck);
};
return (
<View>
<Text>
Select the box to change the image
</Text>
<TouchableOpacity onPress={() => changeImage()}>
<Image
source={
{ check } === true
? require("../img/unchecked.png")
: require("../img/checked.png")
}
/>
</TouchableOpacity>
</View>
);
};
export default SignUpScreen;
Remove {} around the check
const SignUpScreen = () => {
const [check, setCheck] = useState(false);
const changeImage = () => {
setCheck((prevCheck) => !prevCheck);
};
return (
<View>
<Text>
Select the box to change the image
</Text>
<TouchableOpacity onPress={() => changeImage()}>
<Image
source={
check === true
? require("../img/unchecked.png")
: require("../img/checked.png")
}
/>
</TouchableOpacity>
</View>
);
};
export default SignUpScreen;
Otherwise it is an object created everytime
I am trying to get the measurement of the tabs.
Here is the data for this test app. I am creating ref here
const images = {
man: 'https://www.gogole.com',
....
};
const data = Object.keys(images).map(i => ({
key: i,
title: i,
image: images[i],
ref: React.createRef(),
}));
Now I am trying to get the measurement of each tab like this
const Tab = React.forwardRef(({item}, ref) => {
return (
<View ref={ref} key={item.title}>
<Text>{item.title}</Text>
</View>
);
});
const Tabs = ({data, scrollX}) => {
const containerRef = useRef({});
React.useEffect(() => {
data && data.forEach((item) => {
item.ref.current.measureLayout(
containerRef.current,
(x, y, width, height) => {
console.log(x, y, width, height);
},
);
});
},[]);
return (
<View
style={{
position: 'absolute',
}}>
{data.map((data, index) => (
<Tab key={index} item={data} ref={data.ref} />
))}
</View>
);
};
return (
<View style={styles.container}>
<Tabs data={data} />
</View>
)
As you can see I am trying to console.log the measurement in the Tabs. But the app doesn't return the measurement but always this line
Warning: ref.measureLayout must be called with a node handle or a ref to a native component.
From my research in other GitHub treads , there is something called relativeToNativeNode which is deprecated now. Is my error related to this?
So, to avoid the error and get the measurement. What should I change in the code?
I used the ScrollView's onMomentumScrollEnd handler to determine the current page based on the contentOffset in the recyclerlistview Component.
const [currentPage, setCurrentPage] = useState(0);
const onScrollEnd = (e) => {
let contentOffset = e.nativeEvent.contentOffset;
let viewSize = e.nativeEvent.layoutMeasurement;
let pageNum = Math.floor(contentOffset.x / viewSize.width);
setCurrentPage(pageNum);
};
return (
<View style={{ flex: 1 }}>
<RecyclerListView
rowRenderer={({ item }, index) => <Page item={item} id={index} />}
dataProvider={list}
layoutProvider={layoutProvider}
onMomentumScrollEnd={onScrollEnd}
isHorizontal
pagingEnabled
/>
<View style={{ flex: 0.1, backgroundColor:"gold" }}>
<Text>{`current page: ${currentPage}`}</Text>
</View>
</View>
);
I want to display the current Page inside a Text component, but when currentPage state changes in onMomentumScrollEnd handler the whole app is re-render. I need to re-render only the Text Component, any suggestion for that.
I find a solution. the easiest way is to create a class component and put the displayed component in it, then I reference it and change the current page with ref hook.
class DisplayCurrentPage extends React.Component {
contractor(prop){
super(prop)
state:{currentPage:0}
}
updateCurrentPage = (index) => {this.setState({CurrentPage: index})}
render(){
return(
<View style={{ flex: 0.1, backgroundColor:"gold" }}>
<Text>{`current page: ${this.state.currentPage}`}</Text>
</View>
);
}
};
const reference = createRef();
const onScrollEnd = (e) => {
let contentOffset = e.nativeEvent.contentOffset;
let viewSize = e.nativeEvent.layoutMeasurement;
let pageNum = Math.floor(contentOffset.x / viewSize.width);
reference.current.updateCurrentPage(pageNum);
};
return (
<View style={{ flex: 1 }}>
<RecyclerListView
rowRenderer={({ item }, index) => <Page item={item} id={index} />}
dataProvider={list}
layoutProvider={layoutProvider}
onMomentumScrollEnd={onScrollEnd}
isHorizontal
pagingEnabled
/>
<DisplayCurrentPage ref={reference} />
</View>
);
When the page is changed, only DisplayCurrentPage is re-render.
You can use React.memo which is an alternative to shouldComponentUpdate for functional components. It tells React when to re-render the component based on prev and next props.
const Item = React.memo(({ item }) => {
return (
<View>
........
</View>
);
});
I am rendering a list in React Native which currently has about 900 list items. I'm mapping through the list and rendering one component for each item. It currently takes about 3 seconds for React to do this which is unacceptable - I would like it to be near instant. Props are passed to the list item component from the redux store and the list items are nested inside the React Native ScrollView Component.
How can I can I increase the performance of rendering these components so there is not such a huge lag?
Here is my Contacts component:
class Contacts extends Component {
renderRegisteredUsers = (contacts) => {
return contacts.items.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.user.address_book_name}
surname={''}
phoneNumber={contact.user.phone}
isRegisteredUser={true}
ccId={contact.user.id}
/>
))
}
renderContacts = (contacts) => {
if (contacts) {
return contacts.map((contact, index) => (
<ContactListItem
key={index}
firstName={contact.firstName}
surname={contact.surname}
phoneNumber={contact.phoneNumber}
isRegisteredUser={false}
/>
))
} else {
return (
<>
<Loader />
</>
)
}
}
render() {
return (
<>
<ScrollView>
<Text style={{ fontSize: 22 }}>
Your Contacts Using Fleeting
</Text>
{this.renderRegisteredUsers(this.props.user.registeredContacts)}
<Text style={{ fontSize: 22 }}>
Phone Contacts
</Text>
{this.renderContacts(this.props.user.parsedContacts)}
</ScrollView>
</>
)
}
}
const mapStateToProps = (state) => {
const { user } = state;
return { user }
};
export default connect(mapStateToProps)(Contacts);
And my ContactListItem component:
class ContactListItem extends Component {
constructor(props) {
super(props)
}
handleOnClick = () => {
this.props.calleeId(this.props.ccId)
Actions.TimeInput();
}
render() {
return (
<View style={{ margin: 20, display: "flex", flexDirection: "column", justifyContent: "space-between" }}>
<Text>
{this.props.firstName + ' ' + this.props.surname + ' ' + this.props.phoneNumber}
</Text>
<Icon name="adduser" size={40} color="green" style={{ alignSelf: "flex-end" }} onPress={this.handleOnClick} />
</View>
)
}
}
const mapDispatchToProps = {
calleeId,
};
export default connect(null, mapDispatchToProps)(ContactListItem);
Thanks in advance.
1- You can use PureComponent instead of Component. PureComponent re-renders only when its props change and not re-rendering on each parent re-render. More Information: https://reactjs.org/docs/react-api.html#reactpurecomponent
2- Use unique keys when you're mapping on your items.
3- You can use FlatList instead of ScrollView. It supports Scroll Loading. You can set a number of initial numbers and render the others on scroll. More Information: https://facebook.github.io/react-native/docs/flatlist
const renderItem = ({ item }) => (<Text key={item.key}>{item.key}</Text>);
const getItemLayout = (data, index) => (
{length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}
);
const items = [{ key: 'first' }, { key: 'second'}, ...+1000];
function render () => (
<FlatList
data={items}
renderItem={renderItem}
getItemLayout={getItemLayout}
initialNumToRender={5}
maxToRenderPerBatch={10}
windowSize={10}
/>
);
Look into using pagination instead- https://www.npmjs.com/package/react-paginate, https://www.npmjs.com/package/react-native-pagination. If your contacts are coming from an api request, you will have to look into paginating the requests too.
import React, { Component } from 'react';
import {AppRegistry,StyleSheet,View,FlatList,} from 'react-native';
import ContactItem from './Pages/widgets/ContactItem'; // https://github.com/garrettmac/react-native-pagination/blob/master/ReactNativePaginationExample/Pages/widgets/ContactItem.js
import faker from 'faker';//assuming you have this.
import _ from 'lodash';
import Pagination,{Icon,Dot} from 'react-native-pagination';//{Icon,Dot} also available
export default class ReactNativePaginationExample extends Component {
constructor(props){
super(props);
this.state = {
items: this.props.contacts,
};
}
//create each list item
_renderItem = ({item}) => {
return (<ContactItem index={item.id}
onPressItem={this.onPressItem.bind(this)}
name={item.name}
avatar={item.avatar}
description={item.email}
tag={item.group}
createTagColor
/>)
};
//pressed an item
onPressItem = (item) => console.log("onPressItem:item ",item);
_keyExtractor = (item, index) => item.id;
onViewableItemsChanged = ({ viewableItems, changed }) =>this.setState({viewableItems})
render() {
return (
<View style={[s.container]}>
<FlatList
data={this.state.items}
ref={r=>this.refs=r}//create refrence point to enable scrolling
keyExtractor={this._keyExtractor}//map your keys to whatever unique ids the have (mine is a "id" prop)
renderItem={this._renderItem}//render each item
onViewableItemsChanged={this.onViewableItemsChanged.bind(this)}//need this
/>
<Pagination
// dotThemeLight //<--use with backgroundColor:"grey"
listRef={this.refs}//to allow React Native Pagination to scroll to item when clicked (so add "ref={r=>this.refs=r}" to your list)
paginationVisibleItems={this.state.viewableItems}//needs to track what the user sees
paginationItems={this.state.items}//pass the same list as data
paginationItemPadSize={3} //num of items to pad above and below your visable items
/>
</View>
)
}
};
const s = StyleSheet.create({
container: {
flex: 1,
// backgroundColor:"grey",//<-- use with "dotThemeLight"
},
});
AppRegistry.registerComponent('ReactNativePaginationExample', () => App);
the navigationOptions code like that.
static navigationOptions = ({navigation})=>({
tabBarLabel:'查看',
headerTitle:navigation.state.params.title,
tabBarIcon: ({ tintColor,focused }) => (
<Image style={SKIN.tabImage} source={focused?AppImages.MyPost.lookchoose:AppImages.MyPost.look}/>
),
});
this is my Tab componet,how I can get tabBarLabel and tabBarIcon?
export default class Tab extends Component {
renderItem = (route, index) => {
const {
navigation,
jumpToIndex,
} = this.props;
const focused = index === navigation.state.index;
const color = focused ? this.props.activeTintColor : this.props.inactiveTintColor;
return (
<TouchableOpacity
key={index}
style={styles.tabItem}
onPress={() => jumpToIndex(index)}
>
<View
style={styles.tabItem}>
{this.props.renderIcon(color,focused)}
<Text style={{ color }}>{this.props.getLabel()}</Text>
</View>
</TouchableOpacity>
);
};
render(){
console.log('Tab this.props',this.props);
const {navigation,} = this.props;
const {routes,} = navigation.state;
return (
<View style={styles.tab}>
{routes && routes.map(this.renderItem)}
</View>
);
}
}
I custom Tab,now I want use that but some bug show me.
like that,
imagebug
please help me...
try updating the render method with this code:
render(){
console.log('Tab this.props',this.props);
const {navigation,} = this.props;
const {routes,} = navigation.state;
return (
<View style={styles.tab}>
//pass down the route and the index to the renderItem function
{routes && routes.map((route,index) => this.renderItem(route, index) )}
</View>
);
renderItem = (route, index) => {
const {
navigation,
jumpToIndex,
} = this.props;
const focused = index === navigation.state.index;
const color = focused ? this.props.activeTintColor : this.props.inactiveTintColor;
let TabScene = {
focused:focused,
route:route,
tintColor:color
};
return (
<TouchableOpacity
key={route.key}
style={styles.tabItem}
onPress={() => jumpToIndex(index)}
>
<View
style={styles.tabItem}>
{this.props.renderIcon(TabScene)}
<Text style={{ color }}>{this.props.getLabel(TabScene)}</Text>
</View>
</TouchableOpacity>
);
};