I have a list of items (jobs) and when an item (job) is being selected, a new scene is being opened. I want the ID of the selected item to be passed from the scene with the list to the other scene with the details about the selected item (job) without using Redux.
Router
import React from 'react';
import { Scene, Router } from 'react-native-router-flux';
import JobsList from './components/JobsList';
import Job from './components/Job';
const RouterComponent = () => {
return (
<Router>
<Scene key="jobs" component={JobsList} initial />
<Scene key="Job" component={Job} title="Test" />
</Router>
);
};
export default RouterComponent;
Jobs list
import React, { Component } from 'react';
export default class JobsList extends Component {
render() {
return (
<TouchableOpacity onPress={() => { Actions.Job({ jobId: jobId }) }}>
...
</TouchableOpacity>
);
}
}
Job
import React, { Component } from 'react';
export default class Job extends Component {
constructor() {
super();
this.state = {
job: {}
};
axios.get(
// PROBLEM: this.props.jobId is empty
`http://api.tidyme.dev:5000/${this.props.jobId}.json`,
{
headers: { Authorization: 'Token token=123' }
}
).then(response => this.setState({
job: response.data
}));
}
render() {
return (
<Text>{this.state.job.customer.firstName}</Text>
);
}
}
You should call super(props) if you want to access this.props inside the constructor.
constructor(props) {
super(props);
console.log(this.props);
}
The best practice is defining Components as pure functions:
const Job = ({ job, JobId}) => {
return (
<Text>{job.customer.firstName}</Text>
);
}
otherFunctions() {
...
}
Related
I am new to react and I am building my final year project using it. I am currently trying to display the game times, however when I add a new game or update a game time the component is not getting re-rendered without me manually refreshing the page.
My code is below:
import React from "react";
import { Button, StyleSheet, Text, TextInput, View, ScrollView } from "react-native";
import * as firebase from 'firebase';
class AdvancedSearchScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
games: []
}
this.props.watchUserData();
var database = firebase.database();
var commentsRef = firebase.database().ref('games');
commentsRef.on('child_added', (data) => {
this.state.games.push(data.val());
});
commentsRef.on('child_changed', (data) => {
this.state.games.push(data.val());
});
}
render() {
return(
<ScrollView>
<View style={styles.container}>
{this.state.games.map(type => <Text>{type.gameTime}</Text>)}
</View>
</ScrollView>
);
}
}
export default AdvancedSearchScreen;
In this case I think you should move you state update to a componentDidMount() using the this.state.games.push(data.val()); will not trigger a new render, you need to use the setState() function, something like this should work for you:
class AdvancedSearchScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
games: []
}
}
componentDidMount() {
this.props.watchUserData();
var database = firebase.database();
var commentsRef = firebase.database().ref('games');
commentsRef.on('child_added', (data) => {
const newItem = data.val()
this.setState({games: [...this.state.games, newItem]})
});
commentsRef.on('child_changed', (data) => {
const newItem = data.val()
this.setState({games: [...this.state.games, newItem]})
});
}
render() {
return(
<ScrollView>
<View style={styles.container}>
{this.state.games.map(type => <Text>{type.gameTime}</Text>)}
</View>
</ScrollView>
);
}
}
export default AdvancedSearchScreen;
something like this should force the render.
My problem is That I want to access a Container in a component but it seems to be undefined.
undefined alert image
I am using Unstated and as you can see this is my code in the container file (login-container.js):
import { Container } from 'unstated'
class LoginContainer extends Container {
constructor(props){
super(props)
this.state = {
stepNumber: 0,
}
}
}
export default new LoginContainer()
And this is app.js:
import React, { Component } from 'react'
import { createStackNavigator, createSwitchNavigator } from 'react-navigation'
import { Provider } from 'unstated'
import LoginContainer from './containers/login-container'
import Home from './screens/home'
import Splash from './screens/splash'
import Login from './screens/login'
import Intro from './screens/intro'
export default class App extends Component {
render() {
return (
<Provider inject={[LoginContainer]}>
<AuthStack/>
</Provider>
)
}
}
const SplashStack = createStackNavigator(...)
const AppStack = createStackNavigator(...)
const AuthStack = createStackNavigator(
{
Intro: { screen: Intro},
Login: { screen: Login}
},
{
headerMode: "none",
initialRouteName: "Intro"
}
)
const SwitchNavigator = createSwitchNavigator(...)
And this would be login.js:
import React, { Component } from 'react'
import { Text, View } from 'react-native'
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
// const { state: {stepNumber} } = this.props.loginContainer
alert(this.props.LoginContainer)
return (
<View>
<Text> someText </Text>
</View>
)
}
}
I previously tried to use Subscribe component to inject the container to my app but I got the same thing I am getting here.
Using
- react-native 0.58.6
- react-navigation 2.13.0 (due to some bugs in v3)
- unstated 2.1.1
What's really great about Unstated is how simple it is to implement.
Just wrap your render component in Unstated's <Subscribe to></Subscribe> tags and you're good to go. Whenever you setState() in the Container, all Components that Subscribe to it get re-rendered with the Container's updated state property values available to them.
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
export default class Login extends Component {
constructor(props){
super(props)
}
render() {
return (
<Subscribe to={[LoginContainer, AnotherContainer]}>
{(container, another) => (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
})
</Subscribe>
);
}
}
UPDATE: Or do it in this HOC way. After creating this:
WithUnstated.js
import React, { PureComponent } from "react";
import { Subscribe } from "unstated";
import DefaultStore from "../store/DefaultStore";
const withUnstated = (
WrappedComponent,
Stores = [DefaultStore],
navigationOptions
) =>
class extends PureComponent {
static navigationOptions = navigationOptions;
render() {
return (
<Subscribe to={Stores}>
{(...stores) => {
const allStores = stores.reduce(
(acc, v) => ({ ...acc, [v.displayName]: { ...v } }),
{}
);
return <WrappedComponent {...allStores} {...this.props} />;
}}
</Subscribe>
);
}
};
export default withUnstated;
Then wrap your component like so:
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { Subscribe } from 'unstated';
import LoginContainer from './containers/login-container';
import AnotherContainer from './containers/another-container';
class Login extends Component {
constructor(props){
super(props)
}
render() {
const {LoginContainer: container} = this.props;
return (
<View>
<Text>{container.state.stepNumber}</Text>
</View>
);
}
}
export default withUnstated(Login, [LoginContainer, AnotherContainer])
I am complete noob and am just following a tutorial in react-native on udemy. However, I have reached a wall and cannot find a solution anywhere?
Currently I am getting an error from ESLint showing that state is undefined.
Here is the complete code:
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class AlbumList extends Component {
state = { albums: [] }; //state is underlined
ComponentWillMount() {
axios.get('https:/rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }));
}
renderAlbums() {
render() {
console.log(this.state);
return (
<View>
{this.renderAlbums()}
</View>
**strong text**);
}
}
export default AlbumList;
Has there been any update regarding defining 'state' in React-Native?
Sincerely appreciate the help!
Try this out.
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import axios from 'axios';
class AlbumList extends Component {
constructor(props){
super(props);
this.state = {
albums: []
};
this.renderAlbums = this.renderAlbums.bind(this);
}
componentWillMount() {
axios.get('https:/rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }));
}
renderAlbums() {
return (
<View /> // return your Albums here as you need
);
}
render() {
return (
<View>
{this.renderAlbums()}
</View>
);
}
}
export default AlbumList;
I just have a question regarding react-navigation.
I understand that the navigation props becomes accessible when a screen is rendered from the stacknavigator.
But how do you access the navigation props if the screen is not rendered by the stacknavigator?
Like this:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import {
StyleSheet,
Text,
View,
} from 'react-native';
import Swiper from 'react-native-swiper';
import Menu from './Menu';
class HomeSwiper extends Component {
static propTypes = {
navigation: PropTypes.object,
};
render() {
return (
<Swiper showsButtons>
<View>
<Menu
navigationProps={this.props.navigation}
/>
</View>
<View>
<Text>Hello Swiper</Text>
</View>
</Swiper>
);
}
}
export default HomeSwiper;
Wherein Menu is:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { AsyncStorage, TouchableOpacity, Text, BackHandler, Alert } from 'react-native';
import { StandardContainerIOS } from '../components/Container';
import { StandardButton } from '../components/Buttons/';
class Menu extends Component {
static propTypes = {
navigation: PropTypes.object,
};
static navigationOptions = ({ navigation }) => {
const { params = {} } = navigation.state;
const headerLeft = (
<TouchableOpacity
onPress={params.handleRedirect ? params.handleRedirect : () => null}
>
<Text>Logout</Text>
</TouchableOpacity>
);
return {
headerLeft,
};
};
constructor(props) {
super(props);
this.state = {
email: '',
petName: [],
numberOfPets: '',
};
}
getInitialState() {
return {
petName: ['No Name'],
};
}
async componentWillMount() {
try {
const value = await AsyncStorage.getItem('email');
if (value !== null) {
// We have data!!
}
} catch (error) {
// Error retrieving data
}
this.props.navigation.setParams({
handleRedirect: this.handlePressLogout,
});
this.getEmail();
BackHandler.addEventListener('hardwareBackPress', this.handleBackButton);
}
componentWillUnmount() {
BackHandler.removeEventListener('hardwareBackPress', this.handleBackButton);
}
render() {
return (
<StandardContainerIOS>
<StandardButton backgroundColor="#6D4C41" title="View Pet" onPress={this.handleIndexView} />
<StandardButton backgroundColor="#6D4C41" title="Register Pet" onPress={this.handlePressRegisterPets} />
<StandardButton backgroundColor="#6D4C41" title="Logout" onPress={this.handlePressLogout} />
</StandardContainerIOS>
);
}
}
export default Menu;
I've removed the other function definition to cut the post a little shorter. The Menu screen was working fine when it was rendered from the stacknavigator. Im trying to incorporate swiping in my app.
Any suggestions?
I've been struggling passing a value from one component to another. It's a continuation of the issue from a previous question which was partially resolved: react-native tab navigator search box
I'm using tab navigator and here's my app setup:
index.js (renders tab setup)
router.js
searchHeader.js
tab1.js
tab2.js
etc
In index.js when a tab is changed I'm getting the name of the tab. I want to pass that to searchHeader.js to update the placeholder text.
As searchHeader.js isn't imported into index.js and not a direct child how do I pass it that value?
index.js
import React, { Component } from 'react';
import { Root, Tabs } from './config/router';
import { Alert,View } from 'react-native';
class App extends Component {
constructor(props) {
super(props);
this.state = {
searchText: '',
}
}
_getCurrentRouteName(navState) {
if (navState.hasOwnProperty('index')) {
this._getCurrentRouteName(navState.routes[navState.index])
} else {
if (navState.routeName==='One') {
this.setState({searchText:'Search One'})
}
if (navState.routeName==='Two') {
this.setState({searchText:'Search Two'})
}
if (navState.routeName==='Three') {
this.setState({searchText:'Search Three'})
}
if (navState.routeName==='Four') {
this.setState({searchText:'Search Four'})
}
}
}
render() {
return (
<Root onNavigationStateChange={(prevState, newState) => {
this._getCurrentRouteName(newState)
}} />
)
}
}
export default App;
router.js
...
export const Root = StackNavigator({
Tabs: {
screen: Tabs,
navigationOptions: {
header: <SearchHeader data={'Test'} />
}
},
}, {
mode: 'modal',
});
searchHeader.js
import React, { Component } from 'react';
import { View,Text,Dimensions,Alert } from 'react-native';
import { SearchBar } from 'react-native-elements';
class SearchHeader extends Component {
constructor(props) {
super(props);
this.state = {
placeholder: "Search One"
}
}
render() {
return (
<SearchBar
noIcon
containerStyle={{backgroundColor:'#fff'}}
inputStyle={{backgroundColor:'#e3e3e3',}}
lightTheme = {true}
round = {true}
placeholder={data}
placeholderTextColor = '#000'
/>
);
}
};
export default SearchHeader;
You could perhaps pass it as a navigation prop using the setParams method.
An alternative, depending on the scope of your app, would be to look at a state library such as Redux or MobX - but if it's a small app, it's overkill
For that you can use Redux, you will have a store where you can put shared properties and values,
Then your components can connect to that store and bind its props with the chosen reducer(s) and dispatch actions..
this structure may work:
class Home extends Component {
func(val) {
this.setState({value: val});
}
render() {
return (
<View>
<Two func={(val) => this.func(val)} />
</View>
)
}
}
class Two extends Component {
render() {
return (
<View>
<Button title="set" onPress={() => this.props.func('data')} />
</View>
)
}
}