Proper way of using WebSockets with React Native - reactjs

I'm new to React Native, but very familiar with React. As a beginner I'm looking to setup a connection between a cloud server and react-native with websockets as I've seen in the documentation. Unfortunately, there's no decent example out there that could help me out. This is all that I've got so far:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button
} from 'react-native';
export default class raspberry extends Component {
constructor(props) {
super(props);
this.state = { open: false };
this.socket = new WebSocket('ws://127.0.0.1:3000');
this.emit = this.emit.bind(this);
}
emit() {
this.setState(prevState => ({ open: !prevState.open }))
this.socket.send("It worked!")
}
render() {
const LED = {
backgroundColor: this.state.open ? 'lightgreen' : 'red',
height: 30,
position: 'absolute',
flexDirection: 'row',
bottom: 0,
width: 100,
height: 100,
top: 120,
borderRadius: 40,
justifyContent: 'space-between'
}
return (
<View style={styles.container}>
<Button
onPress={this.emit}
title={this.state.open ? "Turn off" : "Turn on"}
color="#21ba45"
accessibilityLabel="Learn more about this purple button"
/>
<View style={LED}></View>
</View>
);
}
componentDidMount() {
this.socket.onopen = () => socket.send(JSON.stringify({ type: 'greet', payload: 'Hello Mr. Server!' }))
this.socket.onmessage = ({ data }) => console.log(JSON.parse(data).payload)
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10,
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5,
},
});
AppRegistry.registerComponent('raspberry', () => raspberry);
Everything works fine, but when I press the button to send a message, this is the error I get:
Cannot send a message. Unknown WebSocket id 1
I also made a test with a js client and everything worked smooth..looking to see how I could get this fixed or some example sources where I can figure it out.

change the code
socket.send(JSON.stringify({ type: 'greet', payload: 'Hello Mr. Server!' }))
to
this.socket.send(JSON.stringify({ type: 'greet', payload: 'Hello Mr. Server!' }))
it should work.
here is my code to test, based on your code and RN 0.45 (and project generated by create-react-native-app), connects to a public websocket server wss://echo.websocket.org/, on my android it works fine and I can see the websocket server's echo message after I push the button.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button
} from 'react-native';
export default class App extends React.Component {
constructor() {
super();
this.state = {
open: false
};
this.socket = new WebSocket('wss://echo.websocket.org/');
this.emit = this.emit.bind(this);
}
emit() {
this.setState(prevState => ({
open: !prevState.open
}))
this.socket.send("It worked!")
}
componentDidMount() {
this.socket.onopen = () => this.socket.send(JSON.stringify({type: 'greet', payload: 'Hello Mr. Server!'}));
this.socket.onmessage = ({data}) => console.log(data);
}
render() {
const LED = {
backgroundColor: this.state.open
? 'lightgreen'
: 'red',
height: 30,
position: 'absolute',
flexDirection: 'row',
bottom: 0,
width: 100,
height: 100,
top: 120,
borderRadius: 40,
justifyContent: 'space-between'
}
return (
<View style={styles.container}>
<Button onPress={this.emit} title={this.state.open
? "Turn off"
: "Turn on"} color="#21ba45" accessibilityLabel="Learn more about this purple button"/>
<View style={LED}></View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF'
},
welcome: {
fontSize: 20,
textAlign: 'center',
margin: 10
},
instructions: {
textAlign: 'center',
color: '#333333',
marginBottom: 5
}
});

According to documentation you need to add state connected to your component. And send anything only if connected state is true.
export default class raspberry extends Component {
constructor(props) {
super(props);
this.state = {
open: false,
connected: false
};
this.socket = new WebSocket('ws://127.0.0.1:3000');
this.socket.onopen = () => {
this.setState({connected:true})
};
this.emit = this.emit.bind(this);
}
emit() {
if( this.state.connected ) {
this.socket.send("It worked!")
this.setState(prevState => ({ open: !prevState.open }))
}
}
}

After I've done some researches I found that the WebSocket should be
new WebSocket("ws://10.0.2.2:PORT/")
where 10.0.2.2 means the localhost

Related

Need to add avatar and username to post

So, I'm trying to add the user avatar and username to my addPost. I've tried everything I can think of but nothing is working.
I've Tried;
firebase.auth().currentUser.avatar;
But that doesn't work, I'm assuming it's because "avatar" or "username" isn't firebase specific properties for auth.
I've also tried;
get uid() {
return firebase.auth().currentUser;
}
and then adding to addPost
avatar: this.uid.avatar,
username: this.uid.username
That doesn't work either.
I just need the user avatar and username to be available when called on the home screen.
import React from "react";
import { View, StyleSheet, FlatList, Platform, TouchableNativeFeedback, TouchableOpacity, Image, Button } from "react-native";
import Fire from '../../Fire';
import UsernameText from '../../components/Text/DefaultUsernameText';
import CreatedAtText from '../../components/Text/CreatedAtText';
import ThoughtTitleText from '../../components/Text/DefaultThoughtTitle';
import Colors from '../../constants/Colors';
import moment from 'moment';
let TouchableCmp = TouchableOpacity;
if (Platform.OS === 'android' && Platform.Version >= 21) {
TouchableCmp = TouchableNativeFeedback;
}
export default class HomeScreen extends React.Component {
state = {
latestPost: [],
}
displayLatestPost = (latestPost) => {
this.setState({latestPost: latestPost});
console.log("latest Post " + this.state.latestPost);
}
componentDidMount(){
Fire.shared.getPosts(this.displayLatestPost);
console.log("This is the displayLatestPost " + this.state.latestPost);
}
renderLatestPost = (post) => {
return (
<View>
<TouchableCmp onPress={() => {}} style={{flex: 1}}>
<View style={styles.container}>
<View style={styles.infoText}>
<Image style={styles.userAvatar}>{post.avatar}</Image>
<UsernameText style={styles.name} >{post.username}</UsernameText>
<CreatedAtText style={styles.timestamp}>{moment(post.timestamp).fromNow()}</CreatedAtText>
</View>
<View style={styles.container} >
<ThoughtTitleText style={styles.feedItem}>{post.thoughtTitle}</ThoughtTitleText>
</View>
</View>
</TouchableCmp>
</View>
);
};
render() {
return (
<View style={styles.container}>
<Button
onPress={() => {
Fire.shared.signOut();
}}
title="Log out"
/>
<FlatList
showsVerticalScrollIndicator={false}
keyExtractor={item => item.id}
style={styles.feed}
data={this.state.latestPost}
renderItem={( {item, index }) =>
this.renderLatestPost(item, index)
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background
},
feed: {
marginHorizontal: 16
},
feedItem: {
borderRadius: 5,
padding: 2,
flexDirection: "row",
marginVertical: 2
},
name: {
fontSize: 15,
fontWeight: "500",
color: "#454D65"
},
timestamp: {
fontSize: 11,
color: "#C4C6CE",
marginTop: 4
},
post: {
marginTop: 16,
fontSize: 14,
color: "#838899"
},
postImage: {
width: undefined,
height: 150,
borderRadius: 5,
marginVertical: 16
},
container: {
flex:1,
borderRadius: 10,
padding:15,
justifyContent: 'flex-end',
alignItems: 'flex-end'
},
thought: {
color: '#666',
fontSize: 18,
marginBottom: 5,
alignItems: 'center'
},
infoText:{
flexDirection: "row"
},
// icons: {
// flexDirection: 'row'
// },
userAvatar: {
backgroundColor: Colors.subheadings,
borderColor: Colors.accent,
borderWidth:3.5,
backgroundColor: Colors.maintext,
marginEnd: 15,
width: 35,
height: 35,
borderRadius: 20,
}
});
Here's my Fire.js
import FirebaseKeys from "./config/FirebaseKeys";
import firebase from "firebase/app";
import '#firebase/auth';
import 'firebase/database';
import '#firebase/firestore';
import "firebase/storage";
require("firebase/firestore");
class Fire {
constructor() {
firebase.initializeApp(FirebaseKeys);
}
getPosts = async (displayLatestPost) => {
const post = await
this.firestore.collection('thoughts').orderBy('timestamp', ' desc').limit(10).get()
let postArray =[]
post.forEach((post) => {
postArray.push({id: post.id, ...post.data()})
})
displayLatestPost(postArray)
}
addPost = async ({ thoughtTitle, thoughtText, localUri, avatar, username}) => {
const remoteUri = await this.uploadPhotoAsync(localUri, `photos/${this.uid}/${Date.now()}`);
return new Promise((res, rej) => {
this.firestore
.collection("thoughts")
.add({
uid: this.uid,
thoughtTitle,
thoughtText,
image: remoteUri,
timestamp: this.timestamp,
})
.then(ref => {
res(ref);
})
.catch(error => {
rej(error);
});
});
};
uploadPhotoAsync = (uri, filename) => {
return new Promise(async (res, rej) => {
const response = await fetch(uri);
const file = await response.blob();
let upload = firebase
.storage()
.ref(filename)
.put(file);
upload.on(
"state_changed",
snapshot => {},
err => {
rej(err);
},
async () => {
const url = await upload.snapshot.ref.getDownloadURL();
res(url);
}
);
});
};
createUser = async user => {
let remoteUri = null;
try {
await firebase.auth().createUserWithEmailAndPassword(user.email, user.password);
let db = this.firestore.collection("users").doc(this.uid);
db.set({
username: user.username,
email: user.email,
avatar: null
});
if (user.avatar) {
remoteUri = await this.uploadPhotoAsync(user.avatar, `avatars/${this.uid}`);
db.set({ avatar: remoteUri }, { merge: true });
}
} catch (error) {
alert("Error: ", error);
}
};
updateProfile = async user => {
let remoteUri = null;
try {
let db =
this.firestore.collection("users").doc(this.uid);
db.update({
username: user.username,
email: user.email,
avatar: null
});
if (user.avatar) {
remoteUri = await this.uploadPhotoAsync(user.avatar, `avatars/${this.uid}`);
db.set({ avatar: remoteUri }, { merge: true });
}
} catch (error) {
alert("Error: ", error);
}
}
signOut = () => {
firebase.auth().signOut();
};
get firestore() {
return firebase.firestore();
}
/* get username(){
return firebase.auth().currentUser.username;
} */
get uid() {
return (firebase.auth().currentUser || {}).uid;
}
/* get avatar(){
return firebase.auth().currentUser.avatar;
} */
get timestamp() {
return Date.now();
}
}
Fire.shared = new Fire();
export default Fire;
I hope the following information helps.
You can store Name & Profile Picture URL like this:
var user = firebase.auth().currentUser;
// Update User Profile
user.updateProfile({
displayName: "Jane Doe",
photoURL: "https://example.com/1hdfabSesfE/profile.jpg"
}).then(function() {
// Update successful.
}).catch(function(error) {
// An error happened.
});
// Use these parameters inside the add post function
var profilePicture = user.photoURL;
var name = user.displayName;
So, I changed my createUser and updateUser
createUser = async user => {
let remoteUri = null;
try {
await firebase.auth().createUserWithEmailAndPassword(user.email, user.password);
let db = this.firestore.collection("users").doc(this.uid);
db.set({
displayName: user.displayName,
email: user.email,
photoURL: null
});
if (user.photoURL) {
remoteUri = await this.uploadPhotoAsync(user.photoURL, `avatars/${this.uid}`);
db.set({ photoURL: remoteUri }, { merge: true });
}
} catch (error) {
alert("Error: ", error);
}
};
updateProfile = async user => {
let remoteUri = null;
try {
let db = this.firestore.collection("users").doc(this.uid);
db.update({
displayName: user.displayName,
photoURL: user.photoURL
});
if (user.photoURL) {
remoteUri = await this.uploadPhotoAsync(user.photoURL, `avatars/${this.uid}`);
db.set({ photoURL: remoteUri }, { merge: true });
}
} catch (error) {
alert("Error: ", error);
}
}
Then I changed my addPost
addPost = async ({ thoughtTitle, thoughtText, localUri, photoURL, displayName}) => {
const remoteUri = await this.uploadPhotoAsync(localUri, `photos/${this.uid}/${Date.now()}`);
return new Promise((res, rej) => {
this.firestore
.collection("thoughts")
.add({
uid: this.uid,
displayName,
photoURL,
thoughtTitle,
thoughtText,
image: remoteUri,
timestamp: this.timestamp,
})
.then(ref => {
res(ref);
})
.catch(error => {
rej(error);
});
});
};
Thanks for all the help - It's working!.
Here's my account screen where I update the displayName and photoURL
import React from "react";
import { View, Text, StyleSheet, Button, Image, TextInput, TouchableOpacity} from
"react-native";
import Fire from "../../Fire";
import { MaterialIcons } from "#expo/vector-icons"
import * as ImagePicker from "expo-image-picker";
import UserPermissions from "../../utilities/UserPermissions";
import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view';
//import Avatar from '../../components/User/Avatar';
export default class AccountScreen extends React.Component {
state = {
user: {},
updatedUser: {
photoURL: null,
displayName: ""
}
};
unsubscribe = null;
componentDidMount() {
const user = this.props.uid || Fire.shared.uid;
this.unsubscribe = Fire.shared.firestore
.collection("users")
.doc(user)
.onSnapshot(doc => {
this.setState({ user: doc.data() });
});
}
componentWillUnmount() {
this.unsubscribe();
}
handleUpdate = () => {
Fire.shared.updateProfile(this.state.updatedUser);
};
handlePickAvatar = async () => {
UserPermissions.getCameraPermission();
let result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [1, 1],
quality: 0.1
});
if (!result.cancelled) {
this.setState({ updatedUser: { ...this.state.updatedUser, photoURL: result.uri
} });
}
};
render() {
return (
<View style={styles.container}>
<KeyboardAwareScrollView
style={{ flex: 1, width: '100%' }}
keyboardShouldPersistTaps="always">
<View style={styles.container}>
<TouchableOpacity style={styles.avatarContainer} onPress=.
{this.handlePickAvatar}>
<Image
source={
this.state.updatedUser.photoURL
? { uri: this.state.user.photoURL }
: require("../../assets/tempAvatar.jpg")
}
style={styles.avatar}
/>
<MaterialIcons
name="photo-camera"
size={40} color="grey"
style={{ marginTop: 6, marginLeft: 2 }}
/>
</TouchableOpacity>
<View>
<TextInput
style={styles.border}
placeholder= "change username"
onChangeText={displayName => this.setState({ updatedUser: { ...this.state.updatedUser, displayName } })}
value={this.state.updatedUser.displayName}
></TextInput>
</View>
<TouchableOpacity onPress={this.handleUpdate}>
<MaterialIcons name="check" size={24} color="black" />
</TouchableOpacity>
</View>
</KeyboardAwareScrollView>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center'
},
profile: {
marginTop: 64,
alignItems: "center"
},
avatarContainer: {
shadowColor: "#151734",
shadowRadius: 30,
shadowOpacity: 0.4
},
avatar: {
width: 100,
height: 100,
borderRadius: 68
},
name: {
marginTop: 24,
fontSize: 16,
fontWeight: "600"
},
statsContainer: {
flexDirection: "row",
justifyContent: "space-between",
margin: 32
},
stat: {
alignItems: "center",
flex: 1
},
statAmount: {
color: "#4F566D",
fontSize: 18,
fontWeight: "300"
},
statTitle: {
color: "#C3C5CD",
fontSize: 12,
fontWeight: "500",
marginTop: 4
},
border: {
width: 200,
margin: 10,
padding: 15,
fontSize: 16,
borderColor: '#d3d3d3',
borderBottomWidth: 1,
textAlign: 'center'
},
});
Here's my Home.js
import React from "react";
import { View, StyleSheet, FlatList, Platform,
TouchableNativeFeedback,
TouchableOpacity, Image, Button } from "react-native";
import Fire from '../../Fire';
import UsernameText from '
../../components/Text/DefaultUsernameText';
import CreatedAtText from '../../components/Text/CreatedAtText';
import ThoughtTitleText from '
../../components/Text/DefaultThoughtTitle';
import Colors from '../../constants/Colors';
import moment from 'moment';
let TouchableCmp = TouchableOpacity;
if (Platform.OS === 'android' && Platform.Version >= 21) {
TouchableCmp = TouchableNativeFeedback;
}
export default class HomeScreen extends React.Component {
state = {
latestPost: [],
}
displayLatestPost = (latestPost) => {
this.setState({latestPost: latestPost});
console.log("latest Post " + this.state.latestPost);
}
componentDidMount(){
Fire.shared.getPosts(this.displayLatestPost);
console.log("This is the displayLatestPost " + this.state.latestPost);
}
renderLatestPost = (post) => {
return (
<View>
<TouchableCmp onPress={() => {}} style={{flex: 1}}>
<View style={styles.container}>
<View style={styles.infoText}>
<Image style={styles.userAvatar} source={{uri:
post.photoURL}} />
<UsernameText style={styles.name} >{post.displayName}.
</UsernameText>
<CreatedAtText style={styles.timestamp}>.
{moment(post.timestamp).fromNow()}</CreatedAtText>
</View>
<View style={styles.container} >
<ThoughtTitleText style={styles.feedItem}>{post.thoughtTitle}.
</ThoughtTitleText>
</View>
</View>
</TouchableCmp>
</View>
);
};
render() {
return (
<View style={styles.container}>
<Button
onPress={() => {
Fire.shared.signOut();
}}
title="Log out"
/>
<FlatList
showsVerticalScrollIndicator={false}
keyExtractor={item => item.id}
style={styles.feed}
data={this.state.latestPost}
renderItem={( {item, index }) =>
this.renderLatestPost(item, index)
}
/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: Colors.background
},
feed: {
marginHorizontal: 16
},
feedItem: {
borderRadius: 5,
padding: 2,
flexDirection: "row",
marginVertical: 2
},
name: {
fontSize: 15,
fontWeight: "500",
color: "#454D65"
},
timestamp: {
fontSize: 11,
color: "#C4C6CE",
marginTop: 4
},
post: {
marginTop: 16,
fontSize: 14,
color: "#838899"
},
postImage: {
width: undefined,
height: 150,
borderRadius: 5,
marginVertical: 16
},
container: {
flex:1,
borderRadius: 10,
padding:15,
justifyContent: 'flex-end',
alignItems: 'flex-end'
},
thought: {
color: '#666',
fontSize: 18,
marginBottom: 5,
alignItems: 'center'
},
infoText:{
flexDirection: "row"
},
// icons: {
// flexDirection: 'row'
// },
userAvatar: {
backgroundColor: Colors.subheadings,
borderColor: Colors.accent,
borderWidth:3.5,
backgroundColor: Colors.maintext,
marginEnd: 15,
width: 35,
height: 35,
borderRadius: 20,
}
});

How to put the result of an API request into array value in react native?

I have a problem to put the result of an API request into an array value in my react native app.
this is the called function
export function getNewFilmFromApi(page) {
return fetch(
'https://api.themoviedb.org/3/discover/movie?api_key=' +
API_TOKEN +
'&release_date.gte=2019-10-01&release_date.lte=2019-12-31&language=fr&page=' +
page,
)
.then(response => response.json())
.catch(error => console.error(error));
}
and this is how I'm trying to put the returned value into the array :
const projects = [
{
title: getNewFilmFromApi().then(data => {
return data.results[0].title;
}),
image: require('../Images/background.jpg'),
author: 'Liu Yi',
text: 'kkkk',
},
{
title: 'The DM App - Ananoumous Chat',
image: require('../Images/background.jpg'),
author: 'Chad Goodman',
text: 'kjhgfhjkl',
},
{
title: 'Nikhiljay',
image: require('../Images/background.jpg'),
author: "Nikhil D'Souza",
text: 'jjjjjj',
},
];
I can see with console.log that there's a value for data.results[0].title but I can't put it in the array!
This is the error I have when I try to do this :
this is my render function and everything work except the title which I want to return it from API.
render() {
return (
<Container>
<AnimatedMask style={{opacity: this.state.opacity}} />
<Animated.View
style={{
transform: [
{translateX: this.state.pan.x},
{translateY: this.state.pan.y},
],
}}
{...this._panResponder.panHandlers}>
<Project
title={projects[this.state.index].title}
image={projects[this.state.index].image}
author={projects[this.state.index].author}
text={projects[this.state.index].text}
canOpen={true}
/>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
top: 230,
left: 0,
zIndex: -1,
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
transform: [
{scale: this.state.scale},
{translateY: this.state.translateY},
],
}}>
<Project
title={projects[getNextIndex(this.state.index)].title}
image={projects[getNextIndex(this.state.index)].image}
author={projects[getNextIndex(this.state.index)].author}
text={projects[getNextIndex(this.state.index)].text}
/>
</Animated.View>
<Animated.View
style={{
position: 'absolute',
top: 240,
left: 0,
zIndex: -2,
width: '100%',
height: '100%',
justifyContent: 'center',
alignItems: 'center',
transform: [
{scale: this.state.thirdScale},
{translateY: this.state.thridTranslateY},
],
}}>
<Project
title={projects[getNextIndex(this.state.index + 1)].title}
image={projects[getNextIndex(this.state.index + 1)].image}
author={projects[getNextIndex(this.state.index + 1)].author}
text={projects[getNextIndex(this.state.index + 1)].text}
/>
</Animated.View>
</Container>
);
}
}
export default connect(mapStateToProps)(Card);
const Mask = styled.View`
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.25);
z-index: -3;
`;
const AnimatedMask = Animated.createAnimatedComponent(Mask);
const Container = styled.View`
flex: 1;
justify-content: center;
align-items: center;
background: #f0f3f5;
margin-top: 80;
`;
const Text = styled.Text``;
var test;
const projects = [
{
title: getNewFilmFromApi().then(data => {
return data.results[0].title;
}),
image: require('../Images/background.jpg'),
author: 'Liu Yi',
text: 'kkkk',
},
{
title: 'The DM App - Ananoumous Chat',
image: require('../Images/background.jpg'),
author: 'Chad Goodman',
text: 'kjhgfhjkl',
},
{
title: 'Nikhiljay',
image: require('../Images/background.jpg'),
author: "Nikhil D'Souza",
text: 'jjjjjj',
},
];
Can you give any solution to display the value into the array please ?
thank you !
The way you're trying to populate your array with results from an API call will not work.
Your API call is asynchronous and it returns a Promise. Therefore you have to wait for your API call to finish and for the Promise to resolve, then update your projects array when your component renders.
I suggest you store your projects array in a state and make an API call when your component renders for the first time, like in the following simplified example:
function getNewFilmFromApi(page) {
return fetch(`https://jsonplaceholder.typicode.com/todos/1`).then(res =>
res.json()
);
}
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
fetchError: false,
isLoading: true,
projects: [
{ title: null, author: `Liu Yi` },
{ title: `The DM App - Anonymous Chat`, author: `Chad Goodman` },
{ title: `Nikhiljay`, author: `Nikhil D'Souza` }
]
};
}
componentDidMount() {
const handleResponse = data => {
const { title } = data;
// assuming you just want to update
// the first item in the `projects` array
const nextProjects = [...this.state.projects];
nextProjects[0].title = title;
this.setState({
projects: nextProjects,
isLoading: false
});
};
const handleError = err => {
this.setState({
apiError: true,
isLoading: false
});
};
getNewFilmFromApi()
.then(handleResponse)
.catch(handleError);
}
render() {
const { apiError, isLoading, projects } = this.state;
if (isLoading) {
return <p>Loading...</p>;
}
if (!isLoading && apiError) {
return <p>Error loading projects</p>;
}
return <p>{JSON.stringify(projects)}</p>;
}
}
Here's a link to a working example:
CodeSandbox

Why is only the last component in array animating?

Goal: create an OptionFan button that when pressed, rotates on its Z axis, and FanItems release from behind the main button and travel along their own respective vectors.
OptionFan.js:
import React, { useState, useEffect } from 'react';
import { Image, View, Animated, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
import FanItem from './FanItem';
const { height, width } = Dimensions.get('window');
export default class OptionFan extends React.Component {
constructor (props) {
super(props);
this.state = {
animatedRotate: new Animated.Value(0),
expanded: false
};
}
handlePress = () => {
if (this.state.expanded) {
// button is opened
Animated.spring(this.state.animatedRotate, {
toValue: 0
}).start();
this.refs.option.collapse();
this.setState({ expanded: !this.state.expanded });
} else {
// button is collapsed
Animated.spring(this.state.animatedRotate, {
toValue: 1
}).start();
this.refs.option.expand();
this.setState({ expanded: !this.state.expanded });
}
};
render () {
const animatedRotation = this.state.animatedRotate.interpolate({
inputRange: [ 0, 0.5, 1 ],
outputRange: [ '0deg', '90deg', '180deg' ]
});
return (
<View>
<View style={{ position: 'absolute', left: 2, top: 2 }}>
{this.props.options.map((item, index) => (
<FanItem ref={'option'} icon={item.icon} onPress={item.onPress} index={index} />
))}
</View>
<TouchableOpacity style={styles.container} onPress={() => this.handlePress()}>
<Animated.Image
resizeMode={'contain'}
source={require('./src/assets/img/arrow-up.png')}
style={{ transform: [ { rotateZ: animatedRotation } ], ...styles.icon }}
/>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#E06363',
elevation: 15,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.155,
width: width * 0.155
},
icon: {
height: width * 0.06,
width: width * 0.06
},
optContainer: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#219F75',
elevation: 5,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.13,
width: width * 0.13,
position: 'absolute'
}
});
FanItem.js:
import React, { useState } from 'react';
import { Image, Animated, StyleSheet, TouchableOpacity, Dimensions } from 'react-native';
import EStyleSheet from 'react-native-extended-stylesheet';
const { width } = Dimensions.get('window');
export default class FanItem extends React.Component {
constructor (props) {
super(props);
this.state = {
animatedOffset: new Animated.ValueXY(0),
animatedOpacity: new Animated.Value(0)
};
}
expand () {
let offset = { x: 0, y: 0 };
switch (this.props.index) {
case 0:
offset = { x: -50, y: 20 };
break;
case 1:
offset = { x: -20, y: 50 };
break;
case 2:
offset = { x: 20, y: 50 };
break;
case 3:
offset = { x: 75, y: -20 };
break;
}
Animated.parallel([
Animated.spring(this.state.animatedOffset, { toValue: offset }),
Animated.timing(this.state.animatedOpacity, { toValue: 1, duration: 600 })
]).start();
}
collapse () {
Animated.parallel([
Animated.spring(this.state.animatedOffset, { toValue: 0 }),
Animated.timing(this.state.animatedOpacity, { toValue: 0, duration: 600 })
]).start();
}
render () {
return (
<Animated.View
style={
(this.props.style,
{
left: this.state.animatedOffset.x,
top: this.state.animatedOffset.y,
opacity: this.state.animatedOpacity
})
}
>
<TouchableOpacity style={styles.container} onPress={this.props.onPress}>
<Image resizeMode={'contain'} source={this.props.icon} style={styles.icon} />
</TouchableOpacity>
</Animated.View>
);
}
}
const styles = StyleSheet.create({
container: {
justifyContent: 'center',
alignItems: 'center',
borderRadius: 30,
backgroundColor: '#219F75',
elevation: 5,
shadowOffset: {
height: 3,
width: 3
},
shadowColor: '#333',
shadowOpacity: 0.5,
shadowRadius: 5,
height: width * 0.13,
width: width * 0.13,
position: 'absolute'
},
icon: {
height: width * 0.08,
width: width * 0.08
}
});
Implementation:
import React from 'react';
import { StyleSheet, View, Dimensions } from 'react-native';
import Component from './Component';
const { height, width } = Dimensions.get('window');
const testArr = [
{
icon: require('./src/assets/img/chat.png'),
onPress: () => alert('start chat')
},
{
icon: require('./src/assets/img/white_video.png'),
onPress: () => alert('video chat')
},
{
icon: require('./src/assets/img/white_voice.png'),
onPress: () => alert('voice chat')
},
{
icon: require('./src/assets/img/camera.png'),
onPress: () => alert('request selfie')
}
];
const App = () => {
return (
<View style={styles.screen}>
<Component options={testArr} />
</View>
);
};
const styles = StyleSheet.create({
screen: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#E6E6E6'
}
});
export default App;
Problem: The issue is, only the last FanItem item runs its animation. (opacity, and vector translation). before implementing the opacity animation I could tell the first three FanItems did in fact render behind the main button, because I could see them when pressing the main button, as the opacity temporarily changes for the duration of the button click.
My question is 1) why are the first three mapped items not animating? and 2) how to resolve this?
You are storing ref of FanItem in option. but, ref gets overridden in each iteration of map. so, at the end it only stores ref of last FanItem in option. So, first declare one array in constructor to store ref of each FanItem:
constructor(props) {
super(props);
// your other code
this.refOptions = [];
}
Store ref of each FanItem separately like this:
{this.props.options.map((item, index) => (
<FanItem ref={(ref) => this.refOptions[index] = ref} icon={item.icon} onPress={item.onPress} index={index} />
))}
and then to animate each FanItem:
for(var i = 0; i < this.refOptions.length; i++){
this.refOptions[i].expand(); //call 'expand' or 'collapse' as required
}
This is expo snack link for your reference:
https://snack.expo.io/BygobuL3JL

Navigator is not working properly in React Native

I followed one tutorial for react native development tutorial.They developed one IOS app using react native.In there they had used NavigatorIOS for navigation purpose.IOS app is working properly.But when I need to create android app I have to changed it to NavigatorIOS to Navigator.App loaded first view.but later on that is not working.
Here [https://www.raywenderlich.com/126063/react-native-tutorial][1] the link of tutorial which I have followed.How can I change code for Android.?
import React, { Component } from 'react';
import {
StyleSheet,
Text,
TextInput,
View,
TouchableHighlight,
ActivityIndicator,
Image
} from 'react-native';
var SearchResults = require('./SearchResults');
function urlForQueryAndPage(key, value, pageNumber) {
var data = {
country: 'uk',
pretty: '1',
encoding: 'json',
listing_type: 'buy',
action: 'search_listings',
page: pageNumber
};
data[key] = value;
var querystring = Object.keys(data)
.map(key => key + '=' + encodeURIComponent(data[key]))
.join('&');
return 'http://api.nestoria.co.uk/api?' + querystring;
};
class SearchPage extends Component {
constructor(props) {
super(props);
this.state = {
searchString: 'london',
isLoading: false,
message: ''
};
}
onSearchTextChanged(event) {
console.log('onSearchTextChanged');
this.setState({ searchString: event.nativeEvent.text });
console.log(this.state.searchString);
}
_executeQuery(query) {
console.log(query);
this.setState({ isLoading: true });
fetch(query)
.then(response => response.json())
.then(json => this._handleResponse(json.response))
.catch(error =>
this.setState({
isLoading: false,
message: 'Something bad happened ' + error
}));
}
onLocationPressed() {
navigator.geolocation.getCurrentPosition(
location => {
var search = location.coords.latitude + ',' + location.coords.longitude;
this.setState({ searchString: search });
var query = urlForQueryAndPage('centre_point', search, 1);
this._executeQuery(query);
},
error => {
this.setState({
message: 'There was a problem with obtaining your location: ' + error
});
});
}
_handleResponse(response) {
this.setState({ isLoading: false , message: '' });
if (response.application_response_code.substr(0, 1) === '1') {
this.props.navigator.push({
title: 'Results',
component: SearchResults,
passProps: {listings: response.listings}
});
} else {
this.setState({ message: 'Location not recognized; please try again.'});
}
}
onSearchPressed() {
var query = urlForQueryAndPage('place_name', this.state.searchString, 1);
this._executeQuery(query);
}
render() {
var spinner = this.state.isLoading ?
( <ActivityIndicator
size='large'/> ) :
( <View/>);
console.log('SearchPage.render');
return (
<View style={styles.container}>
<Text style={styles.description}>
Search for houses to buy!
</Text>
<Text style={styles.description}>
Search by place-name, postcode or search near your location.
</Text>
<View style={styles.flowRight}>
<TextInput
style={styles.searchInput}
value={this.state.searchString}
onChange={this.onSearchTextChanged.bind(this)}
placeholder='Search via name or postcode'/>
<TouchableHighlight style={styles.button}
underlayColor='#99d9f4'
onPress={this.onSearchPressed.bind(this)}>
<Text style={styles.buttonText}>Go</Text>
</TouchableHighlight>
</View>
<View style={styles.flowRight}>
<TouchableHighlight style={styles.button}
underlayColor='#99d9f4'
onPress={this.onLocationPressed.bind(this)}>
<Text style={styles.buttonText}>Location</Text>
</TouchableHighlight>
</View>
<Image source={require('./Resources/house.png')} style={styles.image}/>
{spinner}
<Text style={styles.description}>{this.state.message}</Text>
</View>
);
}
}
var styles = StyleSheet.create({
description: {
marginBottom: 20,
fontSize: 18,
textAlign: 'center',
color: '#656565'
},
container: {
padding: 30,
marginTop: 80,
alignItems: 'center'
},
flowRight: {
flexDirection: 'row',
alignItems: 'center',
alignSelf: 'stretch'
},
buttonText: {
fontSize: 18,
color: 'white',
alignSelf: 'center'
},
button: {
height: 36,
flex: 1,
flexDirection: 'row',
backgroundColor: '#48BBEC',
borderColor: '#48BBEC',
borderWidth: 1,
borderRadius: 8,
marginBottom: 10,
alignSelf: 'stretch',
justifyContent: 'center'
},
searchInput: {
height: 36,
padding: 4,
marginRight: 5,
flex: 4,
fontSize: 18,
borderWidth: 1,
borderColor: '#48BBEC',
borderRadius: 8,
color: '#48BBEC'
},image: {
width: 217,
height: 138
}
});
module.exports = SearchPage;

How to get state value from constructor in ES6?

I'm trying to move forward with ES6 syntax but I've got an error trying to retrieve state value.
So my question is : how to get the state value in ES6? Here is a part of the code:
constructor(props) {
super(props);
this.state = {
timeElapsed: null,
isRunning: false
}
}
Then when I try to get isRunning state, it gives me this error: Cannot read property 'isRunning' of undefined.
if (this.state.isRunning) {
clearInterval(this.interval);
this.setState({
isRunning: false
});
return
}
Any idea? Thanks.
EDIT (here is the full code):
import React, {Component} from 'react';
import {
Text,
View,
AppRegistry,
StyleSheet,
TouchableHighlight
} from 'react-native';
import Moment from 'moment';
import formatTime from 'minutes-seconds-milliseconds';
class StopWatch extends Component {
constructor(props) {
super(props);
this.state = {
timeElapsed: null,
isRunning: false
}
}
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<View style={styles.timerWrapper}>
<Text style={styles.timer}>{formatTime(this.state.timeElapsed)}</Text>
</View>
<View style={styles.buttonWrapper}>
{this.startStopButton()}
{this.lapButton()}
</View>
</View>
<View style={styles.footer}>
<Text>List of laps</Text>
</View>
</View>
)
}
startStopButton() {
var style = this.state.isRunning ? styles.startButton : styles.stopButton;
return (
<TouchableHighlight style={[styles.button, style]} onPress={this.handleStartPress} underlayColor="gray">
<Text>{this.state.isRunning ? 'Stop' : 'Start'}</Text>
</TouchableHighlight>
)
}
lapButton() {
return (
<TouchableHighlight style={[styles.button, styles.lapButton]} onPress={this.lapPress} underlayColor="gray">
<Text>Lap</Text>
</TouchableHighlight>
)
}
border(color) {
return {
borderColor: color,
borderWidth: 4
}
}
handleStartPress() {
console.log('Start was pressed');
if (this.state.isRunning) {
clearInterval(this.interval);
this.setState({
isRunning: false
});
return
}
var startTime = new Date();
this.interval = setInterval(
()=>{
this.setState({
timeElapsed: new Date() - startTime
})
}, 30
);
this.setState({
isRunning: true
})
}
lapPress() {
console.log('Lap was pressed');
}
}
var styles = StyleSheet.create({
container: { // Main container
flex: 1,
alignItems: 'stretch'
},
header: { // Yellow
flex: 2
},
footer: { // Blue
flex: 3
},
timerWrapper: {
flex: 5,
justifyContent: 'center',
alignItems: 'center'
},
timer: {
fontSize: 60
},
buttonWrapper: {
flex: 3,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center'
},
button: {
borderWidth: 2,
height: 100,
width: 100,
borderRadius: 50,
justifyContent: 'center',
alignItems: 'center'
},
startButton: {
borderColor: 'red'
},
stopButton: {
borderColor: 'green'
},
lapButton: {
borderColor: 'blue'
}
});
// AppRegistry.registerComponent('stopWatch', function() {
// return StopWatch;
// });
AppRegistry.registerComponent('stopwatch', () => StopWatch);
EDIT 2:
Here is a combined solution with and without binding in constructor:
import React, {Component} from 'react';
import {
Text,
View,
AppRegistry,
StyleSheet,
TouchableHighlight
} from 'react-native';
import Moment from 'moment';
import formatTime from 'minutes-seconds-milliseconds';
class StopWatch extends Component {
constructor(props) {
super(props);
this.state = {
timeElapsed: null,
isRunning: false
}
this.startStopButton = this.startStopButton.bind(this)
this.lapButton = this.lapButton.bind(this)
}
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<View style={styles.timerWrapper}>
<Text style={styles.timer}>{formatTime(this.state.timeElapsed)}</Text>
</View>
<View style={styles.buttonWrapper}>
{this.startStopButton()}
{this.lapButton()}
</View>
</View>
<View style={styles.footer}>
<Text>List of laps</Text>
</View>
</View>
)
}
startStopButton() {
var style = this.state.isRunning ? styles.startButton : styles.stopButton;
handleStartPress = () => {
console.log('Start was pressed');
if (this.state.isRunning) {
clearInterval(this.interval);
this.setState({
isRunning: false
});
return
}
var startTime = new Date();
this.interval = setInterval(
()=>{
this.setState({
timeElapsed: new Date() - startTime
})
}, 30
);
this.setState({
isRunning: true
})
}
return (
<TouchableHighlight style={[styles.button, style]} onPress={handleStartPress} underlayColor="gray">
<Text>{this.state.isRunning ? 'Stop' : 'Start'}</Text>
</TouchableHighlight>
)
}
lapButton() {
handleLapPress = () => {
console.log('Lap was pressed');
}
return (
<TouchableHighlight style={[styles.button, styles.lapButton]} onPress={handleLapPress} underlayColor="gray">
<Text>Lap</Text>
</TouchableHighlight>
)
}
border(color) {
return {
borderColor: color,
borderWidth: 4
}
}
}
var styles = StyleSheet.create({
container: { // Main container
flex: 1,
alignItems: 'stretch'
},
header: { // Yellow
flex: 2
},
footer: { // Blue
flex: 3
},
timerWrapper: {
flex: 5,
justifyContent: 'center',
alignItems: 'center'
},
timer: {
fontSize: 60
},
buttonWrapper: {
flex: 3,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center'
},
button: {
borderWidth: 2,
height: 100,
width: 100,
borderRadius: 50,
justifyContent: 'center',
alignItems: 'center'
},
startButton: {
borderColor: 'red'
},
stopButton: {
borderColor: 'green'
},
lapButton: {
borderColor: 'blue'
}
});
AppRegistry.registerComponent('stopwatch', () => StopWatch);
You need to bind your class methods to the correct this. See the facebook documentation on using ES6 classes: https://facebook.github.io/react/docs/reusable-components.html#es6-classes.
To fix your error, bind your methods inside of your classes constructor:
class StopWatch extends Component {
constructor(props) {
super(props);
this.state = {
timeElapsed: null,
isRunning: false
}
this.startStopButton= this.startStopButton.bind(this)
this.lapButton = this.lapButton.bind(this)
this.handleStartPress = this.handleStartPress.bind(this)
this.handleLap = this.handleLap.bind(this)
}
render() {
return (
<View style={styles.container}>
<View style={styles.header}>
<View style={styles.timerWrapper}>
<Text style={styles.timer}>{formatTime(this.state.timeElapsed)}</Text>
</View>
<View style={styles.buttonWrapper}>
{this.startStopButton()}
{this.lapButton()}
</View>
</View>
<View style={styles.footer}>
<Text>List of laps</Text>
</View>
</View>
)
}
startStopButton() {
var style = this.state.isRunning ? styles.startButton : styles.stopButton;
return (
<TouchableHighlight style={[styles.button, style]} onPress={this.handleStartPress} underlayColor="gray">
<Text>{this.state.isRunning ? 'Stop' : 'Start'}</Text>
</TouchableHighlight>
)
}
lapButton() {
return (
<TouchableHighlight style={[styles.button, styles.lapButton]} onPress={this.lapPress} underlayColor="gray">
<Text>Lap</Text>
</TouchableHighlight>
)
}
border(color) {
return {
borderColor: color,
borderWidth: 4
}
}
handleStartPress() {
console.log('Start was pressed');
if (this.state.isRunning) {
clearInterval(this.interval);
this.setState({
isRunning: false
});
return
}
var startTime = new Date();
this.interval = setInterval(
()=>{
this.setState({
timeElapsed: new Date() - startTime
})
}, 30
);
this.setState({
isRunning: true
})
}
lapPress() {
console.log('Lap was pressed');
}
}
sorry for the bad formatting
This has nothing to do with ES6, it's an inherent difficulty with the way Javascript works.
Your error is here:
onPress={this.lapPress}
You are thinking that means "when the button is pressed, invoke the method lapPress on my component". It doesn't. It means "when the button is pressed, invoke the method lapPress from my component, using whatever this happens to be set to".
There are several ways to bind this to its method properly, but the easiest (in ES6) might be
onPress={() => this.lapPress()}

Resources