How to navigate page with React Native - javascript

I have a component for listing items, I want to add the function that can go to a different page, and that page has the detail about that item. Currently, this is my code for listing items.
import React, { Component } from 'react';
import { ScrollView } from 'react-native';
import axios from 'axios';
import CarDetail from './CarDetail';
const API_URL = 'http://localhost:3000';
class CarList extends Component {
state = { cars: [] };
componentWillMount() {
console.log('Mount');
axios.get(`${API_URL}/cars`)
.then(response => this.setState({ cars: response.data.cars }));
}
renderCars() {
return this.state.cars.map(car => <CarDetail key={car.id} car={car} />
);
}
render() {
console.log(this.state.cars);
return (
<ScrollView>
{this.renderCars()}
</ScrollView>
);
}
}
export default CarList;
and this is the code for describing items
import React from 'react';
import { Text, View, Image } from 'react-native';
import { Actions } from 'react-native-router-flux';
import Card from '../material/Card';
import CardSection from '../material/CardSection';
const CarDetail = ({ car }) => {
const imageURI = 'https://yt3.ggpht.com/-HwO-2lhD4Co/AAAAAAAAAAI/AAAAAAAAAAA/p9WjzQD2-hU/s900-c-k-no-mo-rj-c0xffffff/photo.jpg';
const { make, model } = car;
function showCarDetail() {
Actions.showCar();
}
return (
<Card>
<CardSection>
<View style={styles.containerStyle}>
<Image
style={styles.imageStyle}
source={{ uri: imageURI }}
/>
</View>
<View style={styles.headContentStyle}>
<Text
style={styles.headerTextStyle}
onPress={showCarDetail()}
>
{make}
</Text>
<Text>{model}</Text>
</View>
</CardSection>
<CardSection>
<Image
style={styles.picStyle}
source={require('./car.jpg')}
/>
</CardSection>
</Card>
);
};
const styles = {
headContentStyle: {
flexDirection: 'column',
justifyContent: 'space-around'
},
headerTextStyle: {
fontSize: 18
},
imageStyle: {
height: 50,
width: 50
},
containerStyle: {
justifyContent: 'center',
alignItems: 'center',
marginLeft: 10,
marginRight: 10
},
picStyle: {
height: 300,
flex: 1,
width: null
}
};
export default CarDetail;
How can I change my code for that? Can anyone give me an example?

You have to use some sort of navigation component. There are many out there, but personally I use the one that is built into React Native. https://facebook.github.io/react-native/docs/navigator.html

Related

I cannot use "this" on a function

I am new to React Native. I want to create a simple counter button. I could not use "this", it gives error ('this' implicitly has type 'any' because it does not have a type annotation.). You can see my TabTwoScreen.tsx TypeScript code below. I searched other questions but i could not find what to do. Why this is not working and how can I correct it. Waiting for helps. Thanks a lot.
import * as React from 'react';
import { StyleSheet, Button, Alert } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
export default function TabTwoScreen() {
const state={
counter: 0,
}
const but1 = () => {
this.setState({counter : this.state.counter + 1});
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{state.counter}</Text>
<Button
title="Increment"
onPress={but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
separator: {
marginVertical: 30,
height: 1,
width: '80%',
},
});
Error Message
App Output:
import React, { useState } from 'react';
import { StyleSheet, Button, Alert, Text, View } from 'react-native';
export default function TabTwoScreen() {
// ๐Ÿ‘‡ You are using functional components so use the useState hook.
const [counter, setCounter] = useState(0);
const but1 = () => {
// ๐Ÿ‘‡then you can increase the counter like below
setCounter(counter + 1);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{counter}</Text>
<Button
title="Increment"
onPress={but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
});
And if you want to use Class based component then here is the implementation:
import React, { useState, Component } from 'react';
import { StyleSheet, Button, Alert, Text, View } from 'react-native';
export default class TabTwoScreen extends Component {
constructor(props) {
super(props);
this.state = {
counter: 0,
};
}
but1 = () => {
this.setState({ counter: this.state.counter + 1 });
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{this.state.counter}</Text>
<Button
title="Increment"
onPress={this.but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
title: {
fontSize: 20,
fontWeight: 'bold',
},
});
Working App: Expo Snack
Please take a look at https://reactjs.org/docs/react-component.html#setstate
Remove this from your code and let only state.count + 1
An example I did in freedcodecamp.
handleChange(event){
//event.target.value
this.setState({
input: event.target.value
});
}
// Change code above this line
render() {
return (
<div>
{ /* Change code below this line */}
<input value={this.state.input} onChange={(e) => this.handleChange(e)}/>
That is because you are using a function component. You have to either use a class based component (React class and function components) or switch over to React hooks with the useState hook.
Here's the example with a class based component:
import * as React from 'react';
import { StyleSheet, Button, Alert } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
export default class TabTwoScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
counter: 0
}
}
but1() {
this.setState({ counter: this.state.counter + 1 });
};
render() {
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{state.counter}</Text>
<Button
title="Increment"
onPress={this.but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}
}
And here's with React hooks:
import React, {useState} from 'react';
import { StyleSheet, Button, Alert } from 'react-native';
import EditScreenInfo from '../components/EditScreenInfo';
import { Text, View } from '../components/Themed';
export default function TabTwoScreen() {
const [counter, setCounter] = useState(0);
const but1 = () => {
setCounter(counter + 1);
};
return (
<View style={styles.container}>
<Text style={styles.title}>Counter:{counter}</Text>
<Button
title="Increment"
onPress={but1}
accessibilityLabel="increment"
color="blue"
/>
</View>
);
}

Use a variable inside a render of react native

I need to use the url from a json and render it on the screen, using React Native and axios.
I'm getting the url with this:
teste() {
api.get('photos').then(
function imagens(response) {
var i = 1;
for (i; i < 2; i++) {
urlDaImagem = response.data[i].url
console.log('em cima->' + urlDaImagem)
}
return urlDaImagem
})
}
I need to put that "urlDaImagem" inside of a Image component.
The entire code:
import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Image,
StatusBar
} from 'react-native';
import api from '../api';
var urlDaImagem
class PaginaPrincipal extends React.Component {
teste() {
api.get('photos').then(
function imagens(response) {
var i = 1;
for (i; i < 2; i++) {
urlDaImagem = response.data[i].url
console.log('em cima->' + urlDaImagem)
}
return urlDaImagem
})
}
render() {
return (
<>
<StatusBar barStyle="dark-content" />
<SafeAreaView>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.sideBySideImages}>
<Image style={styles.imagens} source={{ uri: urlDaImagem }} />
</View>
</ScrollView>
</SafeAreaView>
</>
)
}
}
const styles = StyleSheet.create({
imagens: {
width: "45%",
height: 183,
marginBottom: 5,
marginTop: 5,
marginStart: "3%",
marginEnd: "1%"
},
sideBySideImages: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap'
}
});
export default PaginaPrincipal;
To load data from API it is better to use useEffect hook (React native supports it):
https://reactjs.org/docs/hooks-effect.html#explanation-why-effects-run-on-each-update
Also, React to work properly should control the state of your components. You have to declare state by useState hook, for example. Global vars won't work without tricks)
function PaginaPrincipal(props) {
const [urlDaImagem, setUrlDaImagem] = useState();
useEffect(
() => {
api.get('photos').then(({ data }) => {
console.log(data);
const [{ url }] = data;
setUrlDaImagem(url);
});
},
[] // Effect does not depend on any props
);
return (
<>
...
<Image style={styles.imagens} source={{ uri: urlDaImagem }} />
...
</>
);
}
you can use state in your case, read about it from here: https://reactjs.org/docs/state-and-lifecycle.html
and I updated your code try to test it, and see the best life cycle to call teste function and this should do the trick
import React from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Image,
StatusBar
} from 'react-native';
import api from '../api';
class PaginaPrincipal extends React.Component {
state={
urlDaImagem:""
}
teste() {
api.get('photos').then(
function imagens(response) {
var i = 1;
let urlDaImagem;
for (i; i < 2; i++) {
urlDaImagem = response.data[i].url
console.log('em cima->' + urlDaImagem)
}
this.setState({urlDaImagem})
})
}
render() {
return (
<>
<StatusBar barStyle="dark-content" />
{ this.state.urlDaImagem &&
<SafeAreaView>
<ScrollView contentInsetAdjustmentBehavior="automatic">
<View style={styles.sideBySideImages}>
<Image style={styles.imagens} source={{ uri: this.state.urlDaImagem }} />
</View>
</ScrollView>
</SafeAreaView>
}
</>
)
}
}
const styles = StyleSheet.create({
imagens: {
width: "45%",
height: 183,
marginBottom: 5,
marginTop: 5,
marginStart: "3%",
marginEnd: "1%"
},
sideBySideImages: {
flex: 1,
flexDirection: 'row',
flexWrap: 'wrap'
}
});
export default PaginaPrincipal;

Unable to navigate to next screen in React Native

I have created a list of posts and want to pass details of one specfic post to another screen. I want to be able to click on the post and be directed to the post details screen. This is the PostList.js file:
import React, {Component} from 'react';
import {
FlatList,
StyleSheet,
View,
Text,
Modal,
TouchableOpacity,
} from 'react-native';
import Post from './Post';
import Firebase from 'firebase';
import 'firebase/database';
import {firebaseConfig} from './configFirebase';
import PostDetails from './stack/PostDetails';
export default class Posts extends Component {
constructor(props) {
super(props);
!Firebase.apps.length
? Firebase.initializeApp(firebaseConfig.firebase)
: Firebase.app();
this.state = {
postList: [],
navigation: this.props.navigation,
};
}
state = {
loading: false,
currentPost: null,
};
componentDidMount() {
this.getPostData();
}
getPostData = () => {
const ref = Firebase.database().ref('/posts');
ref.on('value', snapshot => {
console.log('DATA RETRIEVED');
const postsObject = snapshot.val();
if (!postsObject) {
return console.warn('No data from firebase');
}
const postsArray = Object.values(postsObject);
this.setState({postList: postsArray});
});
};
render() {
return (
<View style={styles.container}>
<FlatList
keyExtractor={post => post.heading}
data={this.state.postList}
renderItem={({item: post}) => (
<Post
key={post.heading}
heading={post.heading}
description={post.description}
location={post.location}
onPress={() => this.props.navigation.push('PostDetails', {post})}
/>
)}
/>
</View>
);
}
}
export const styles = StyleSheet.create({
container: {
borderWidth: 2,
borderRadius: 5,
backgroundColor: '#2bb76e',
flex: 1,
},
txtInput: {
flex: 1,
margin: 5,
padding: 5,
borderWidth: 2,
fontSize: 20,
borderRadius: 5,
backgroundColor: 'snow',
},
});
I've tried navigation.navigate() and navigation.push() and neither work.
This is the PostDetails Screen I want to navigate to and pass the post info to:
import React from 'react';
import {Text, View} from 'react-native';
export default ({route}) => {
const postInfo = route.params.post;
return (
<View>
<Text>{JSON.stringify(postInfo, null, 2)}</Text>
<Text>{postInfo.heading}</Text>
</View>
);
};
This is my HomeStack file where the screens are kept:
import React from 'react';
import {NavigationContainer} from '#react-navigation/native';
import {createStackNavigator} from '#react-navigation/stack';
import Posts from '../PostList';
import AddForm from '../AddForm';
import PostDetails from './PostDetails';
const HomeStack = createStackNavigator();
const HomeStackScreen = () => (
<HomeStack.Navigator>
<HomeStack.Screen
name="PostList"
component={Posts}
options={{headerTitle: 'big APPetite'}}
/>
<HomeStack.Screen
name="PostDetails"
component={PostDetails}
options={({route}) => ({heading: route.params.post.heading})}
/>
<HomeStack.Screen name="NewPost" component={AddForm} />
</HomeStack.Navigator>
);
export default HomeStackScreen;
Post.js:
import React, {Component} from 'react';
import {
Image,
Text,
StyleSheet,
View,
TextInput,
FlatList,
TouchableOpacity,
} from 'react-native';
import FavouriteButton from './buttons/FavouriteButton';
import chickenClub from './images/chickenSandwich.jpg';
const Post = ({heading, description, location, username}) => (
<TouchableOpacity style={postStyle.container}>
<View style={(postStyle.container, {alignItems: 'flex-start'})}>
<View style={postStyle.padding}>
<Image style={postStyle.image} source={chickenClub} />
<View style={{backgroundColor: (255, 255, 255, 45), borderRadius: 6}}>
<Text style={postStyle.text}>{heading}</Text>
<Text style={postStyle.text}>{location}</Text>
<Text style={postStyle.text}>{username}*username*</Text>
</View>
</View>
<View
style={{
alignSelf: 'flex-end',
flexDirection: 'column',
backgroundColor: '#2bb76e',
}}>
<Text style={postStyle.paragraph}>{description}</Text>
<View style={{justifyContent: 'flex-start', alignItems: 'flex-end'}}>
<FavouriteButton />
</View>
</View>
</View>
</TouchableOpacity>
);
const postStyle = StyleSheet.create({
container: {
borderWidth: 2,
borderRadius: 5,
backgroundColor: '#2bb76e',
flex: 1,
},
padding: {
padding: 10,
},
heading: {
backgroundColor: (255, 250, 250, 50),
flexDirection: 'column',
},
paragraph: {
alignSelf: 'flex-end',
fontSize: 20,
},
username: {},
image: {
flexDirection: 'row',
height: 150,
width: 150,
},
text: {
fontSize: 25,
padding: 5,
},
});
export default Post;
You are passing onPress to your Post component, however you are not applying that prop to the TouchableOpacity.
Modifying the Post component to include:
const Post = ({heading, description, location, username, onPress}) => (
<TouchableOpacity style={postStyle.container} onPress={onPress}>

React Native changing background color according to data that comes through http request from server

I am new at React Native.
I'm working on a code that will change the background color of a box (card) according to the data that I will get from API. I want to check first if the title is like 'Taylor' make background red , if it is 'Fearless' make it green and so on.
Here is the API that I got information from :
http://rallycoding.herokuapp.com/api/music_albums
This is the code divided into several files.
First of them index.js
// Import a library to help to create a component
import React from 'react';
import { Text, AppRegistry, View } from 'react-native';
import Header from './src/components/header.js';
import AlbumList from './src/components/AlbumList.js'
// create a component
const App = () => (
<View>
<Header headerText={'Smart Parking'}/>
<AlbumList />
</View>
);
//render it to the device
AppRegistry.registerComponent('albums2', () => App);
second is AlbumList.js
import React, { Component } from 'react';
import { View } from 'react-native';
import axios from 'axios';
import AlbumDetail from './AlbumDetail.js'
class AlbumList extends Component {
state = { albums: [] };
componentWillMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({ albums: response.data }) );
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
export default AlbumList;
3rd is AlbumDetail.js
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
4th is card which I need to change background of it
import React from 'react';
import { View } from 'react-native';
const Card = (props) => {
return (
<View style={styles.containerStyle}>
{props.children}
</View>
);
};
const styles = {
containerStyle:{
borderWidth: 1,
borderRadius: 2,
backgroundColor: '#ddd',
borderBottomWidth: 0,
shadowColor: '#000',
shadowOffset: {width: 0, height:2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
last one is header
// Import libraries for making components
import React from 'react';
import { Text, View } from 'react-native';
// make a components
const Header = (props) => {
const { textStyle, viewStyle } = styles;
return(
<View style={viewStyle}>
<Text style={textStyle}>{props.headerText}</Text>
</View>
)
};
const styles ={
viewStyle:{
backgroundColor:'orange',
justifyContent: 'center',
alignItems: 'center',
height: 60,
},
textStyle: {
fontSize: 20
}
};
// make the component to the other part of the app
export default Header;
Basically you need to pass the title of the album as prop to the Card from the AlbumDetails component and then on each Card calculate the color to use and pass it in the style like this:
// AlbumDetails.js component
import React from 'react';
import {Text, View} from 'react-native';
import Card from './Card.js'
const AlbumDetail = (props) => {
return(
<Card title={props.album.title}>
<Text> {props.album.title} </Text>
</Card>
);
};
export default AlbumDetail;
// Card.js component
import React from "react";
import { View } from "react-native";
function calculateColor(title) {
let bgColor;
switch (title) {
case "Taylor":
bgColor = "red";
break;
case "Fearless":
bgColor = "green";
break;
default:
bgColor = "orange";
break;
}
return bgColor;
}
const Card = props => {
const { title } = props;
const backgroundColor = calculateColor(title);
return (
<View style={[styles.containerStyle, { backgroundColor: backgroundColor }]}>
{props.children}
</View>
);
};
const styles = {
containerStyle: {
borderWidth: 1,
borderRadius: 2,
backgroundColor: "#ddd",
borderBottomWidth: 0,
shadowColor: "#000",
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 2,
elevation: 1,
marginLeft: 5,
marginRight: 5,
marginTop: 10
}
};
export default Card;
Something like this should work:
Change the AlbumDetail to conditionally render the Card.
const AlbumDetail = props => {
if (props.album.title === 'Taylor') {
return (
<Card style={{ backgroundColor: 'red' }}>
<Text>{props.album.title}</Text>
</Card>
);
} else {
return (
<Card style={{ backgroundColor: 'green' }}>
<Text>{props.album.title}</Text>
</Card>
);
}
};
Override the default style of the card using the passed style prop.
const Card = props => {
return (
<View style={[styles.containerStyle, props.style]}>{props.children}</View>
);
};
accepted answer is great just its a bad habit to do things in render.
i also not sure since every title has a color why wouldnt the server send this in the object props in first place ? :)
class AlbumList extends Component {
state = { albums: [] };
componentDidMount() {
axios.get('https://rallycoding.herokuapp.com/api/music_albums')
.then(response=> Array.isArray(response.data) ? response.data : []) // alittle validation would not hurt :) !
.then(data => this.setState({ albums: data }) );
}
selectHeaderColorForAlbum( album ){
let headerColor = 'red';
// switch(album.title){ ..you logic }
return headerColor;
}
renderAlbums() {
return this.state.albums.map(album =>
<AlbumDetail key={album.title} album={album} color={this.selectHeaderColorForAlbum(album)} />
);
}
render() {
return(
<View>
{this.renderAlbums()}
</View>
);
}
}
const Card = (props) => {
return (
<View style={[styles.containerStyle,{color:props.headerColor}]}>
{props.children}
</View>
);
};
this is easier your logic will render only once.
also notice that react >16.0 depricated componentWillMount, so use DidMount

How to render a loader until data is fetched in React Native

I am fetching data through an async request. I know that I need to wait for the api request to complete before displaying the data. Unfortunately, I'm not sure how to create a loader to wait for the data to load.I am new to react, so if I could also get help with implementing it as well, that would be fantastic! Here is my current code:
import React, { Component, PropTypes } from 'react';
import { View, Text, ListView, StyleSheet, TouchableHighlight} from 'react- native';
import Header from '../Components/Header';
import Api from '../Utility/Api';
export default class CalendarPage extends Component {
constructor(props) {
super(props);
}
async componentWillMount() { this.setState(
{data: await Api.getDates()},
)
}
render() {
return (
<View style={{flex: 1}}>
<Header pageName="Calendar" navigator={this.props.navigator}/>
<View style = {{flex:9}}>
<View>
{ this.state.data.days[0].items.map((item) => (
<View>
<Text>{item.summary}</Text>
<Text>{item.start.dateTime}</Text>
<Text>{item.description}</Text>
</View>
))}
</View>
</View>
</View>
);
}
}
A simple example using ActivityIndicator -
import ActivityIndicator
import { View, Text, ListView, StyleSheet, TouchableHighlight, ActivityIndicator} from 'react- native';
set data state to null
constructor(props) {
super(props);
this.state = {
data: null
}
}
do conditional rendering
render() {
if (!this.state.data) {
return (
<ActivityIndicator
animating={true}
style={styles.indicator}
size="large"
/>
);
}
return (
<View style={{flex: 1}}>
<Header pageName="Calendar" navigator={this.props.navigator}/>
....
....
</View>
);
}
}
indicator style
const styles = StyleSheet.create({
indicator: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
height: 80
}
});
Although solution proposed by #vinayr works fine but user will still be able to click on screen and perform some action even while loader is being shown which can lead to crash.
One solution is wrap loader inside a Modal.
import React, { Component } from 'react';
import {
StyleSheet,
View,
Modal,
ActivityIndicator,
} from 'react-native';
const styles = StyleSheet.create({
modalBackground: {
flex: 1,
alignItems: 'center',
flexDirection: 'column',
justifyContent: 'space-around',
backgroundColor: '#00000040',
},
activityIndicatorHolder: {
backgroundColor: '#FFFFFF',
height: 100,
width: 100,
borderRadius: 10,
display: 'flex',
alignItems: 'center',
justifyContent: 'space-around',
},
});
const SmartLoader = (props) => {
const {
isLoading,
...attributes
} = props;
return (
<Modal
transparent
animationType={'none'}
visible={isLoading}
onRequestClose={() => { console.log('Noop'); }}
>
<View style={styles.modalBackground}>
<View style={styles.activityIndicatorHolder}>
<ActivityIndicator
animating={isLoading}
size="large"
/>
</View>
</View>
</Modal>
);
};
export default SmartLoader;
After that you can use it anywhere in your component, user will not be able to perform any action till loader is finished ( made hidden based on flag)

Categories

Resources