React Native newbie here.
I'm trying to build a simple React Native app for practice that is essentially just a multi-page contact form.
I'm having trouble figuring out the best way to pass props / state to my child components when I'm rendering them via the navigator (navigatorRenderScene function)
Whenever I try to assign props to my component here, it allows me to pass a string but not a function or an object. For example in the component First, I'm trying to pass a string and a function. Only the string will hold its value once the page is rendered.
Am I doing this completely the wrong way? Do I need to look into some kind of state management system like Flux or Redux? Maybe even the redux router package? (Haven't even touched these yet to be honest)
The routing is all working great, it's just the props / state that I can't figure out.
Here's my index
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';
import { Container, Header, Title, Content, List, ListItem, InputGroup, Input, Button, Icon, Picker } from 'native-base'
// a bunch of crap here being imported that I don't need, I'll trim it down later.
import First from './components/First'
import Second from './components/Second'
class App extends Component {
constructor(props) {
super(props);
this.state = {
text : 'initial state',
sentBy : '',
company : '',
phoneNumber : ''
};
this.testFunc = this.testFunc.bind(this)
}
// end constructor
mostRecentPush() {
// firebase stuff, not really important to the problem
console.log('pushing input data to DB')
firebase.database().ref('mostRecent/').set({
'body' : this.state.text,
'sentBy' : this.state.sentBy,
'location' : this.state.pickerValue,
'company' : this.state.company
});
firebase.database().ref('emails/').push({
'body' : this.state.text,
'sentBy' : this.state.sentBy,
'location' : this.state.pickerValue,
'company' : this.state.company
})
}
// end mostRecentPush() / firebase stuff
onButtonPress() {
this.props.navigator.push({
id: 'Second'
})
} // end onButtonPress
testFunc() {
console.log('calling this from the index')
}
render() {
console.log('rendering home')
return (
<Navigator
initialRoute = {{
id: 'First'
}}
renderScene={
this.navigatorRenderScene
}
/>
)
} // end render
navigatorRenderScene(route, navigator) {
_navigator = navigator;
switch (route.id){
case 'First':
return(<First testString="cat123" testFunc={this.testFunc} navigator={navigator} title="First" />)
// passing our navigator value as props so that the component has access to it
case 'Second':
return(<Second navigator={navigator} title="Second" />)
case 'Third':
return(<Third navigator={navigator} title="Third" />)
case 'Fourth':
return(<Fourth navigator={navigator} title="Fourth" />)
case 'Fifth':
return(<Fifth navigator={navigator} title="Fifth" />)
} //end switch
} // end navigatorRenderScene
} // end component
AppRegistry.registerComponent('App', () => App);
And here's an example component, First
import React, { Component } from 'react';
import {
AppRegistry,
Navigator,
StyleSheet,
Text,
View,
TouchableHighlight
} from 'react-native';
import { Container, Header, Title, Content, List, ListItem, InputGroup, Input, Button, Icon, Picker } from 'native-base'
// too much stuff being imported, will clean this up later
class First extends Component {
constructor(props) {
super(props)
this.onButtonPress = this.onButtonPress.bind(this)
}
onButtonPress() {
this.setState({
text: 'changed state from first component'
})
console.log(this.state)
this.props.navigator.push({
id: 'Second'
})
}
render() {
return(
<Container>
{console.log('rendering first')}
<Content>
<List>
<ListItem>
<InputGroup>
<Input
placeholder='Name'
/>
</InputGroup>
</ListItem>
</List>
<TouchableHighlight onPress={this.onButtonPress}>
<Text>Go to Page 2</Text>
</TouchableHighlight>
</Content>
</Container>
)
}
}
export default First
I think the the problem it's with the scope. When setting the renderScene prop to the navigator, you are not binding that function to the actual class, therefore when the navigatorRenderScene gets executed, the scope it's not the same as testFunc.
Try changing this line of code:
navigatorRenderScene(route, navigator) {
//...
}
For this:
navigatorRenderScene = (route, navigator) => {
// ...
}
The arrow function will bind the navigatorRenderScene to the class, therefore this.testFunc will exist there.
Other options to do the binding
If you don't want to use an arrow function, you could try something like this in the constructor.
// First options
this.navigatorRenderScene = this.navigatorRenderScene.bind(this)
Or you can also use the bind method directly on the callback
// Second option
renderScene={
this.navigatorRenderScene.bind(this)
}
Or an arrow function as well
// Third option
renderScene={
(route, navigator) => this.navigatorRenderScene(route, navigator)
}
Let me know if this works. Good luck!
Related
I wanna know the best way to set the params and options for react native navigation in a class component.
note that the same params are used in options.
when I put all code in the constructor I got params undefined because of timing issue.
and it works. for me in one case when I added option in componentDidMount , I will write some examples in the code below.
1- first case using class component (it's working)
type Props = {
navigation: NavigationProp<any>;
route: RouteProps<{ Example: {title: string} }, 'Example'>
}
export default class Example extends Component <Props> {
constructor(props: Props){
super(props)
this.props.navigation.setParams({ title: 'title' });
}
componentDidMount(){
this.props.navigation.setOptions({ title: this.props.route.params.title })
}
...
}
2 - second case using FC: (not using this example but I think it's also the best way todo for the FC).
export function Example: React.FC = () => {
const navigation = useNavigation();
const route = useRoute();
useLayoutEffect(()=>{
navigation.setParams({ title: 'title' });
navigation.setOptions({ title: route.params.title })
})
...
}
so I hope my question is clear, is that theright way to set Header options with the lates Navigation on React Native?
constructor is the first step in component lifecycle, and you are setting params inside that, which means there is a prop that is going to be updated.
so we need a function that understands every update on a state or received props, and that listener is nothing except "componentDidUpdate(){}" 🤟:
import {NavigationProp, RouteProp} from '#react-navigation/native';
import React, {Component} from 'react';
import {Text, StyleSheet, View} from 'react-native';
type Props = {
navigation: NavigationProp<any>;
route: RouteProp<{Example: {title: string}}, 'Example'>;
};
export default class Example extends Component<Props> {
constructor(props: Props) {
super(props);
this.props.navigation.setParams({title: 'title'});
}
componentDidUpdate() {
this.props.navigation.setOptions({title: this.props.route.params.title});
}
render() {
return (
<View style={styles.container}>
<Text style={styles.textStyle}>Use component did update :)</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
padding: 30,
},
textStyle: {
color: 'black',
fontSize: 20,
fontWeight: 'bold',
},
});
I am trying to build a search bar that filters through an array of users that are placed in a grid using Material-Ui styling. I have tried multiple times to map through and display the users but I am unable to render the page with the users displayed in a grid.
I keep getting an error saying "User is not Defined"
Here is the UserList.js code
// implemented a class based component
import React, { Component } from 'react';
import './UserList.css'
// we will be using this for our cards
import Grid from '#material-ui/core/Grid';
import TextField from '#material-ui/core/TextField';
import * as contentful from 'contentful';
class UserList extends Component {
// state object contains two properties
state = {
users: [
{
"id": 1,
"name": "Mary Ann",
"socialmedia": "Instagram",
"following": "401k"
},
{
"id": 2,
"name": "Jessica P",
"socialmedia": "Instagram",
"following": "600k"
}
],
searchString: ''
}
constructor() {
// super is the parent consturctor
super()
// we are making sure the list is loaded at the beginning of the lifecycle
this.getUsers()
}
// here we are implementing that method after we retreived it above
getUsers = () => {
// client.getEntries
({
// what data are we returning
content_type: 'user',
// we use query because we will have a search filter activated
// with this we will also need to make sure whent he user searches, the search barupdates accordingly
query: this.state.searchString
})
// returns a promise
// once there is a response, we use that callback function to make sure we ae setting the state again
.then((response) => {
this.setState({users: response.elements})
})
// in case there is an error we want to catch it
.catch((error) => {
console.log("Error occured while fetching data")
console.log(error)
})
}
// we are passing an event as a parameter
onSearchInputChange = (event) => {
// if the value entered by the user in the textfield, make sure the state is updated
if (event.target.value) {
this.setState({searchString: event.target.value})
// if that is not the case,we set the state to an empty string
} else {
this.setState({searchString: ''})
}
// we are making sure so the query is executed again and we are making sure only the users are returned, matching to our search stirng
this.getUsers()
}
// The View
// now we are making sure it is rendered in the browser
render() {
return (
<div>
{this.state.users ? (
<div>
<TextField style={{padding: 24}}
id="searchInput"
placeholder="Search for Influencers"
margin="normal"
onChange = {this.onSearchInputChange} />
<Grid container spacing={24} style={{padding: 24}}>
{ this.state.users.map(currentUser => (
<Grid item xs={12} sm={6} lg={4} xl={3}>
<User user={currentUser} />
</Grid>
))}
</Grid>
</div>
) : "No courses found" }
</div>
)
}
}
export default UserList;
Here is the User.js Code
import React from 'react';
import Card from '#material-ui/core/Card';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia '
import Button from '#material-ui/core/Button';
import Typography from '#material-ui/core/button';
const User = (props) => {
return (
<div>
<h1 className='profile'> #HASHTAG HOUND </h1>
{this.state.users.map((element, index) => {
return <div>
<h1 className='profile'>{element.name}</h1>
<h2 className='profile'>{element.socialmedia}</h2>
<h2 className='profile'>{element.following}</h2>
</div>
})}
</div>
)
}
}
export default User;
It seems like the problem is the User Component is not being imported inside the UserList.js file. So simply import it: import User from './User'; (set it to the correct path).
Issue resolved. Click here to watch the => WORKING DEMO
You were trying to get users data from UsersList file which is not possible by importing that file, you need to create another file where you put the data and use the data in both the files by exporting the data from the file and just import the const user from that file usersListData.jsx as shown on the working demo and apply map on that const.
I am having problem figuring out why my application is doing endless render.
Inside, My stateful component, I am calling a redux action in componentDidMount method (calling componentWillMount also do endless render)
class cryptoTicker extends PureComponent {
componentDidMount() {
this.props.fetchCoin()
// This fetches some 1600 crypto coins data,Redux action link for the same in end
}
render() {
return (
<ScrollView>
<Header />
<View>
<FlatList
data={this.state.searchCoin ? this.displaySearchCrypto : this.props.cryptoLoaded}
style={{ flex: 1 }}
extraData={[this.displaySearchCrypto, this.props.cryptoLoaded]}
keyExtractor={item => item.short}
initialNumToRender={50}
windowSize={21}
removeClippedSubviews={true}
renderItem={({ item, index }) => (
<CoinCard
key={item["short"]}
/>
)}
/>
</View>
</ScrollView>
)
}
}
In CoinCard I am literally doing nothing besides this (Notice CoinCard inside Flat list)
class CoinCard extends Component {
render () {
console.log("Inside rende here")
return (
<View> <Text> Text </Text> </View>
)
}
}
Now, When I console log in my coincard render, I can see infinite log of Inside rende here
[Question:] Can anyone please help me figure out why this could be happening?
You can click here to see my actions and click here to see my reducer.
[Update:] My repository is here if you want to clone and see it by yourself.
[Update: 2]: I have pushed the above shared code on github and it will still log endless console.log statements (if you can clone, run and move back to this commit ).
[Update:3]: I am no longer using <ScrollView /> in <FlatList /> also when I mean endless render, I mean is that it is endless (& Unecessarily) passing same props to child component (<Coincard />), if I use PureComponent, it won't log endlessly in render () { but in componentWillRecieveProps, If I do console.log(nextProps), I can see the same log passed over and over again
There are some points to note in your code.
The CoinCard Component must be a PureComponent, which will not re-render if the props are shallow-equal.
You should not render your Flatlist inside the ScrollView component, which would make the component render all components inside it at once which may cause more looping between the Flatlist and ScrollView.
You can also a definite height to the rendered component to reduce the number of times component is rendered for other props.
Another thing to note is, only props in the component are rendered on scroll bottom, based on the log statement mentioned below.
import {Dimensions} from 'react-native'
const {width, height} = Dimensions.get('window)
class CoinCard extends React.PureComponent {
render () {
console.log(this.props.item.long) //... Check the prop changes here, pass the item prop in parent Flatlist. This logs component prop changes which will show that same items are not being re-rendered but new items are being called.
return (
<View style={{height / 10, width}}> //... Render 10 items on the screen
<Text>
Text
</Text>
</View>
)
}
}
UPDATE
This extra logging is due to the props being from the Flatlist to your component without PureComponent shallow comparison.
Note that componentWillReceiveProps() is deprecated and you should avoid them in your code.
React.PureComponent works under the hood and uses shouldComponentUpdate to use shallow comparison between the current and updated props. Therefore log console.log(this.props.item.long) in your PureComponent' render will log the unique list which can be checked.
Like izb mentions, the root cause of the pb is the business call that is done on a pure component whereas it is just loaded. It is because your component make a business decision (<=>"I decide when something must be showed in myself"). It is not a good practice in React, even less when you use redux. The component must be as stupid a possible and not even decide what to do and when to do it.
As I see in your project, you don't deal correctly with component and container concept. You should not have any logic in your container, as it should simply be a wrapper of a stupid pure component. Like this:
import { connect, Dispatch } from "react-redux";
import { push, RouterAction, RouterState } from "react-router-redux";
import ApplicationBarComponent from "../components/ApplicationBar";
export function mapStateToProps({ routing }: { routing: RouterState }) {
return routing;
}
export function mapDispatchToProps(dispatch: Dispatch<RouterAction>) {
return {
navigate: (payload: string) => dispatch(push(payload)),
};
}
const tmp = connect(mapStateToProps, mapDispatchToProps);
export default tmp(ApplicationBarComponent);
and the matching component:
import AppBar from '#material-ui/core/AppBar';
import IconButton from '#material-ui/core/IconButton';
import Menu from '#material-ui/core/Menu';
import MenuItem from '#material-ui/core/MenuItem';
import { StyleRules, Theme, withStyles, WithStyles } from '#material-ui/core/styles';
import Tab from '#material-ui/core/Tab';
import Tabs from '#material-ui/core/Tabs';
import Toolbar from '#material-ui/core/Toolbar';
import Typography from '#material-ui/core/Typography';
import AccountCircle from '#material-ui/icons/AccountCircle';
import MenuIcon from '#material-ui/icons/Menu';
import autobind from "autobind-decorator";
import * as React from "react";
import { push, RouterState } from "react-router-redux";
const styles = (theme: Theme): StyleRules => ({
flex: {
flex: 1
},
menuButton: {
marginLeft: -12,
marginRight: 20,
},
root: {
backgroundColor: theme.palette.background.paper,
flexGrow: 1
},
});
export interface IProps extends RouterState, WithStyles {
navigate: typeof push;
}
#autobind
class ApplicationBar extends React.PureComponent<IProps, { anchorEl: HTMLInputElement | undefined }> {
constructor(props: any) {
super(props);
this.state = { anchorEl: undefined };
}
public render() {
const auth = true;
const { classes } = this.props;
const menuOpened = !!this.state.anchorEl;
return (
<div className={classes.root}>
<AppBar position="fixed" color="primary">
<Toolbar>
<IconButton className={classes.menuButton} color="inherit" aria-label="Menu">
<MenuIcon />
</IconButton>
<Typography variant="title" color="inherit" className={classes.flex}>
Title
</Typography>
<Tabs value={this.getPathName()} onChange={this.handleNavigate} >
{/* <Tabs value="/"> */}
<Tab label="Counter 1" value="/counter1" />
<Tab label="Counter 2" value="/counter2" />
<Tab label="Register" value="/register" />
<Tab label="Forecast" value="/forecast" />
</Tabs>
{auth && (
<div>
<IconButton
aria-owns={menuOpened ? 'menu-appbar' : undefined}
aria-haspopup="true"
onClick={this.handleMenu}
color="inherit"
>
<AccountCircle />
</IconButton>
<Menu
id="menu-appbar"
anchorEl={this.state.anchorEl}
anchorOrigin={{
horizontal: 'right',
vertical: 'top',
}}
transformOrigin={{
horizontal: 'right',
vertical: 'top',
}}
open={menuOpened}
onClose={this.handleClose}
>
<MenuItem onClick={this.handleClose}>Profile</MenuItem>
<MenuItem onClick={this.handleClose}>My account</MenuItem>
</Menu>
</div>
)}
</Toolbar>
</AppBar>
</div >
);
}
private getPathName(): string {
if (!this.props.location) {
return "/counter1";
}
return (this.props.location as { pathname: string }).pathname;
}
private handleNavigate(event: React.ChangeEvent<{}>, value: any) {
this.props.navigate(value as string);
}
private handleMenu(event: React.MouseEvent<HTMLInputElement>) {
this.setState({ anchorEl: event.currentTarget });
}
private handleClose() {
this.setState({ anchorEl: undefined });
}
}
export default withStyles(styles)(ApplicationBar);
Then you will tell me: "but where do I initiate the call that will fill my list?"
Well I see here that you use redux-thunk (I prefer redux observable... more complicated to learn but waaaaaaaaaaaaaaaaay more powerful), then this should be thunk that initiates the dispatch of this!
To summarize:
Components: the stupidest element that normally should have only the render method, and some other method handler to bubble up user events. This method only takes care of showing its properties to the user. Don't use the state unless you have a visual information that belongs only to this component (like the visibility of a popup for example). Anything that is showed or updated comes from above: a higher level component, or a container. It doesn't decide to update its own values. At best, it handles a user event on a subcomponent, then bubble up another event above, and... well maybe at some point, some new properties will be given back by its container!
Container: very stupid logic that consists in wrapping a top level component into redux for it to plug events to actions, and to plug some part of the store to properties
Redux thunk (or redux observable): it is the one that handles the whole user application logic. This guy is the only one who knows what to trigger and when. If a part of your front end must contain the complexity, it's this one!
Reducers: define how to organize the data in the store for it to be as easily usable as possible.
The store: ideally one per top level container, the only one that contains the data that must be showed to the user. Nobody else should.
If you follow these principles, you should never face any issue like "why the hell this is called twice? and... who made it? and why at this moment?"
Something else: if you use redux, use an immutability framework. Otherwise you may face issues as reducers must be pure functions. For this you can use a popular one immutable.js but not convenient at all. And the late ousider that is actually a killer: immer (made by the author or mobx).
It seems Jacob in the above comment has managed to make the component render only twice.
This will definitely cause double initial render (and would cause an infinite render if it wasn't a PureComponent):
componentDidUpdate() {
var updateCoinData;
if (!updateCoinData) { // <- this is always true
updateCoinData = [...this.props.cryptoLoaded];
this.setState({updateCoinData: true}); // <- this will trigger a re render since `this.state.updateCoinData` is not initially true
}
...
}
Link to the issue in your repository
I have a screen inside my react-navigation StackNavigator that looks like this:
import React from 'react';
import { FlatList, ScrollView, StyleSheet, Text, View } from 'react-native';
import { List, ListItem } from 'react-native-elements';
import Accordion from '#ercpereda/react-native-accordion';
export default class PassportScreen extends React.Component {
static navigationOptions = {
title: 'Passport Recovery',
};
constructor(props) {
super(props);
this.renderItem = this.renderItem.bind(this);
}
renderItem(item) {
return (
<View>
<Accordion
header={item.item.key}
content={item.item.content}
/>
</View>
)
}
render() {
const instructions = [
{
key: <Text>1. Fill out a passport application form</Text>,
content: <Text>Content</Text>
},
{
key: <Text>2. Fill out a lost/missing passport statement</Text>,
content: <Text>Content</Text>
},
///...etc
];
return (
<ScrollView>
<FlatList
data={instructions}
renderItem={this.renderItem}
/>
</ScrollView>
)
}
}
module.exports = PassportScreen;
however, when I click to navigate to this screen from another screen, I get this error: TypeError: this.props.header is not a function. (In 'this.props.header({
isOpen: this.state.is_visible
})', 'this.props.header' is an instance of Object).
Other questions I've looked at with similar errors have mentioned passing props to the constructor and needing to pass this.renderItem instead of this.renderItem(), both of which I have already done, so I'm wondering if the problem comes from the fact that this screen is inside a StackNavigator and is navigated to by clicking on a ListItem. Is my intuition correct? If so, how can I fix this?
It seems that the header prop accepts a function, rather than just a component like content does.
Right now you're directly passing an object to the header prop, therefore it won't accept the callback function.
You may try the following approach in order to pass a callback to the header.
PassportScreen.js
customFunc = (callback) => {
console.log(callback)
}
renderItem = (item) => { // Useful to bind `this`
return (
<View>
<Accordion
header={this.customFunc}
content={item.item.content}
/>
</View>
)
}
ChildComponent.js
this.props.header('I'm setting the callback here')
I am attempting to show some info based on what item was pressed.
To do so I am modifying the state's selectedSchedule value to different values according to which button was pressed.
The info(for now, the selectedSchedule value itself) displayed is always the one that should've been before. When you press 'Segmented' the displayed text is '', the value selectedSchedule was initialized with. Then, when you go back, and press, 'Uberman' 'Segmented appears'.
I don't know if this is an issue with the component lifecycle or if javascript/React handle functions asynchronously; the function written after setState() seems to get called first.
//SleepSchedules.js
import React, { Component} from 'react';
import { Container, Content, Card, CardItem, Text, Icon, Button } from 'native-base';
import ScheduleItem from './ScheduleItem';
export default class SleepSchedules extends Component {
constructor(props) {
super(props);
this.state = {selectedSchedule: ''};
}
_handlePress(schedule){
this.setState({
selectedSchedule: schedule
});
this._navScheduleItem()
}
_navScheduleItem(){
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: {scheduleName: this.state.selectedSchedule}
})
}
render() {
return (
<Container style={{paddingTop:64}}>
<Content>
<Card>
<CardItem button
onPress={()=> this._handlePress('Segmented')} >
<Text>Segmented</Text>
</CardItem>
<CardItem button
onPress={()=> this._handlePress('Everyman')}>
<Text>Everyman</Text>
</CardItem>
<CardItem button
onPress={()=> this._handlePress('Uberman')}>
<Text>Uberman</Text>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
Here is the component it's passing it's props to:
import React, { Component} from 'react';
import { Container, Content, Card, CardItem, Text, Icon, Button } from 'native-base';
import ComingSoon from './ComingSoon';
export default class ScheduleItem extends Component {
constructor(props){
super(props);
}
render(){
return(
{this.props.scheduleName}
);
}
}
Simple, setState is async in this case (it is sync, in some special cases, lol, right?). That means, never rely on setState being sync...
There are 2 possible solutions.
First: Don't wait until state is changed. Pass received value manually, immediately.
_handlePress(selectedSchedule) {
this.setState({ selectedSchedule });
this._navScheduleItem(selectedSchedule)
}
_navScheduleItem(scheduleName) {
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: { scheduleName },
})
}
Second: Wait until state change propagate. Read updated state value.
_handlePress(selectedSchedule){
this.setState({ selectedSchedule }, () => {
this._navScheduleItem()
});
}
_navScheduleItem(){
this.props.navigator.push({
title: 'ScheduleItem',
component: ScheduleItem,
passProps: { scheduleName: this.state.selectedSchedule }
})
}