I am new to React Native. I am trying to build an app which has a Splash screen that would later navigate to Login screen if a user has not been authenticated or the Main screen if the user is authenticated. This is done using this.props.navigation.navigate()
The problem is that the Splash component would be mounted twice. I checked this by printing inside componentDidMount() of Splash. Because of this, the Login/Main screen enters twice, which looks very unpleasant. Is there any way to fix this?
Also, I want to add some delay when the screen changes from Splash to Login or Main using setTimeout(). Anyway to go about doing this?
Here's my code:
index.js
import React from 'react';
import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import { persistStore } from 'redux-persist';
import reduxThunk from 'redux-thunk';
import reducers from './src/reducers';
import { StyleSheet } from 'react-native';
import LoginScreen from './src/components/Login/LoginScreen';
import Splash from './src/components/Login/Splash';
import Navigation from './src/components/Navigation/Navigation';
import { Font } from 'expo';
import {
createStackNavigator
} from 'react-navigation';
const createStoreWithMiddleware = applyMiddleware(reduxThunk)(createStore);
const store = createStoreWithMiddleware(reducers);
const persistor = persistStore(store);
export default class App extends React.Component {
constructor(props){
super(props);
this.state = {
fontLoaded: false,
currentScreen: 'Splash',
};
setTimeout(() => this.setState({currentScreen: 'Login'}), 2000);
}
async componentDidMount() {
await Font.loadAsync({
'Quicksand': require('./assets/fonts/Quicksand-Regular.ttf'),
'Quicksand-Medium': require('./assets/fonts/Quicksand-Medium.ttf'),
'Quicksand-Bold': require('./assets/fonts/Quicksand-Bold.ttf'),
});
this.setState({ fontLoaded: true });
}
render() {
const MainNavigator = createStackNavigator({
Splash: { screen: Splash },
Main: { screen: Navigation },
Login: { screen: LoginScreen },
})
if (this.state.fontLoaded)
return (
<Provider store={store}>
<MainNavigator></MainNavigator>
</Provider>
)
else return null;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
Splash.js
import React from 'react';
import { StyleSheet, Text, View, ImageBackground, Image, Button } from 'react-native';
import bgImage from '../../../assets/images/login-background2.png';
import logo from '../../../assets/images/app-logo.png';
import { connect } from 'react-redux';
import { checkAuth } from '../../actions/auth.actions';
class Splash extends React.Component {
static navigationOptions ={
header: null
}
constructor(props){
super(props);
this.state = {
stillLoading: true,
}
}
componentDidMount() {
this.props.checkAuth();
}
render() {
if (this.props.authState.isLoginPending)
return (
<ImageBackground source={bgImage} style={styles.backgroundContainer}>
<View style={styles.logoContainer}>
<Image source={logo} style={styles.logo}></Image>
<Text style={styles.logoText}> Welcome to HealthScout</Text>
</View>
</ImageBackground>
);
else if (this.props.authState.isLoginSuccess){
setTimeout(() => this.props.navigation.navigate('Main'));
return null;
}
else{
setTimeout(() => this.props.navigation.navigate('Login'));
return null;
}
}
}
const mapStateToProps = state => {
return {
authState: state.authState
}
}
const mapDispatchToProps = dispatch => {
return {
checkAuth: () => dispatch(checkAuth()),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Splash);
const styles = StyleSheet.create({
backgroundContainer: {
flex: 1,
alignItems: 'center',
width: null,
height: null,
justifyContent: 'center',
},
logoContainer: {
alignItems: 'center',
},
logo: {
width: 110,
height: 149,
},
logoText: {
color: '#fff',
fontSize: 40,
fontFamily: 'Quicksand',
opacity: 0.7,
marginTop: 20,
marginBottom: 10,
textAlign: 'center',
},
});
Solution
Take out the createStackNavigator from render.
It is better way wrapping screens above App class.
const MainNavigator = createStackNavigator({
Splash: { screen: Splash },
Main: { screen: Navigation },
Login: { screen: LoginScreen },
})
export default class App extends React.Component {
...
Why?
render is run repeatedly depends on various conditions as changing state, props and so on.
And your code looks making multiple components with createStackNavigation in render. Take out :)
p.s If you want to wait loading fonts before show home screen, just change to home screen from splash screen after loaded fonts. Thus, the better way is loading fonts in SplashScreen and do what you want.
Related
After optimising my images iv realised that I still need more time for my components to load. They are card like components with images.
I have 2 components to load one is in a flatList, the other just a basic card like component each component contains images. I have been trying in vain to get this to work and have to ask if anyone has a good solution. Here's what I have so far.
import React, { useState } from "react";
import { View, StyleSheet } from "react-native";
import AppLoading from "expo-app-loading";
import Header from "./components/Header";
import HomeScreen from "./screens/HomeScreen";
const fetchHomeScreen = () => {
return HomeScreen.loadAsync({
HomeScreen: require("./screens/HomeScreen"),
Header: require("./components/Header"),
});
};
export default function App() {
const [HomeScreenLoaded, setHomeScreenLoaded] = useState(false);
if (!HomeScreenLoaded) {
return (
<AppLoading
startAsync={fetchHomeScreen}
onFinish={() => setHomeScreenLoaded(true)}
onError={(err) => console.log(err)}
/>
);
}
return (
<View style={styles.screen}>
<Header title="Your Beast Log" />
<HomeScreen />
</View>
);
}
const styles = StyleSheet.create({
screen: {
flex: 1,
backgroundColor: "#3E3636",
},
});
Are you using expo status bar ?? then try these;
import splash screen
import * as SplashScreen from 'expo-splash-screen';
add this to the top of main function
SplashScreen.preventAutoHideAsync()
export default function App() {....}
then add this to your screen where you hide the splash screen
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
if(appIsready) {
(async () => {
await SplashScreen.hideAsync();
})()
}
},[appIsReady])
async function prepare() {
try {
await fetchHomeScreen;
//OR
await HomeScreen.loadAsync({
HomeScreen: require("./screens/HomeScreen"),
Header: require("./components/Header"),
});
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
I am trying the increment and decrement tutorial . When I type a number at the textinput, this value is to be reflected to the this text given. But I am getting error. The error says
undefined is not a function(evaluating 'this.props.counterSet(count)')
These are the codes that I have tried. Can anybody tell me where I have done the mistake.
Thank You
- App
-Actions
-ActionTypes.js
-CounterAction.js
-Reducers
-counterReducer.js
app.js
counterReducer.js
export default (state = 0, action) => {
switch (action.type) {
case 'SET':
return action.payload;
default:
return state;
}
}
counterAction.js
export const counterSet = (receivedNumber) => {
return {
type: 'SET',
payload: receivedNumber
}
}
ActionTypes.js
export * from './CounterAction';
app.js
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, TextInput, View, Button } from 'react-native';
import { connect } from 'react-redux';
import { counterSet } from './Actions/ActionTypes';
class App extends Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.onChangeText = this.onChangeText.bind(this);
}
onChangeText(number) {
let count = parseInt(number);
// alert("inside", count);
this.props.counterSet(count);
}
render() {
return (
<View style={styles.container}>
<TextInput
style={{ width: 40, height: 40, borderWidth: 1 }}
onChangeText={this.onChangeText}
value={this.props.count.toString()}
/>
<View style={styles.countViewStyle}>
<Text style={styles.welcome}>
{this.props.count}
</Text>
</View>
</View>
);
}
}
function mapStateToProps(state) {
return {
count: state
}
}
export default connect(mapStateToProps, { counterIncrement, counterDecrement, counterClear, counterSet })(App);
Change the import from
export * from './CounterAction';
to
export { counterSet } from './CounterAction;
Hope this will help!
I want to require() a static image in a Home screen component I've written, and I want that image to be the full width of the display (which here happens to also be the full width of the container) while maintaining the aspect ratio.
Here is what I have so far. The Home component:
import React, { Component } from "react";
import {
StatusBar,
} from "react-native";
import Container from "../components/Container/Container";
import Header from "../components/Header/Header";
import FullWidthImage from "../components/Images/FullWidthImage";
export default class Home extends Component {
render() {
return (
<Container>
<FullWidthImage source={require("./lifting.jpg")}/>
<Header/>
<StatusBar translucent={false}/>
</Container>
);
}
}
And the FullWidthImage component:
import React, { Component } from "react";
import { View, Image, StyleSheet } from "react-native";
import Proptypes from "prop-types";
export default class FullWidthImage extends Component {
render() {
return (
<Image source={this.props.source} style={style.image} resizeMode="contain"/>
);
}
}
const style = StyleSheet.create({
image: {
flex: 1,
width: "100%",
borderColor: "red", borderWidth: 2,
}
});
I've been trying for a while and looking around on SO for a good answer but I can't find one (for an example some of them assume you have an uri and are not using require()). Greatly appreciate the help!
Here is what I ended up doing:
I created the following component
import React, { Component } from "react";
import { View, Image, StyleSheet, Dimensions } from "react-native";
import Proptypes from "prop-types";
export default class FullWidthImage extends Component {
static propTypes = {
requireSource: Proptypes.number // is actually a require(some-url)
};
render() {
let image = this.props.requireSource;
const { width, height } = Image.resolveAssetSource(image);
const ratio = height / width;
const SCREEN_WIDTH = Dimensions.get('window').width;
return (
<Image source={image} style={{width: SCREEN_WIDTH, height: SCREEN_WIDTH*ratio}} resizeMode="contain"/>
);
}
}
And then I use it in my Home component like this:
This way I can have the image I want to show in a component in the same folder as that component.
Note: The formatting of this answer seems messed up, don't know how to fix it, I tried ctrl+k on the code snippets.
You can use the not so well documented library resolveAssetSource.
Like this:
import {
Platform,
StyleSheet,
Text,
Image,
View,
Dimensions,
} from 'react-native';
var resolveAssetSource = require('resolveAssetSource');
var imgSrc = resolveAssetSource(require('./200x150.png'));
console.log(imgSrc.width, imgSrc.height);
const SCREEN_WIDTH = Dimensions.get('window').width;
const IMAGE_RATIO = imgSrc.height/imgSrc.width;
...
<Image source={imgSrc}
style={{width:SCREEN_WIDTH, height:SCREEN_WIDTH*IMAGE_RATIO}}/>
...
The important being: var imgSrc = resolveAssetSource(require('./200x150.png'));
I used this issue to get the answer, and then I went and try it ; )
https://github.com/facebook/react-native/issues/4025
Use resizeMode (https://facebook.github.io/react-native/docs/image.html#style)
resizeMode: "cover"
In your code:
...
const style = StyleSheet.create({
image: {
flex: 1,
width: "100%",
borderColor: "red", borderWidth: 2,
resizeMode: "cover"
},
container: {flex:1}
});
Or try with other types: contain, cover, stretch, center, repeat.
I have an array of texts that I want to flash on a blank screen, one after the other with animations. Something like:
state = {
meditations: ["Take a deep breath", "embrace this feeling", "breath
deeply", ...]
}
I want to show only one string at a time, and animate their opacity. So a string fades in and fades out, then the next string, and so on.
I am new to react native and quite confused about how to go about this. Please, how may I approach this, I have read the docs but still not clear how to.
Below is what I have tried, I modified this from the docs but it shows everything at once. I'm still trying to see how I can make them animate one after the other, showing only one at a time. Thanks for your help in advance.
import React from 'react';
import { Animated, Text, View } from 'react-native';
class FadeInView extends React.Component {
state = {
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
}
renderMeditations() {
let { fadeAnim } = this.state;
return this.props.meditations.map((meditation, index) => {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 2, // Animate to opacity: 1 (opaque)
duration: 10000, // Make it take a while
}
).start(() => {
this.setState({ fadeAnim: new Animated.Value(0) })
}); // Starts the animation
return (
<Animated.Text // Special animatable View
key={index}
style={{
...this.props.style,
opacity: fadeAnim, // Bind opacity to animated value
}}
>
{meditation}
</Animated.Text>
)
})
}
render() {
return (
<View style={{flex: 1}}>
{this.renderMeditations()}
</View>
);
}
}
export default class App extends React.Component {
state = {
meditations: ["Take a deep breath", "Calm down", "Relax", "Tell yourself all will be fine"]
}
render() {
return (
<View style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
<FadeInView meditations={this.state.meditations} style={{fontSize: 28, textAlign: 'center', margin: 10}} />
</View>
)
}
}
After much toil with this, I was able to solve it with react-native-animatable like so:
import React from "react";
import {
View,
Text,
Animated
} from "react-native";
import * as Animatable from 'react-native-animatable';
class VideoScreen extends React.Component {
state = {
meditations: ["Take a deep breath", "embrace this feeling", "breath
deeply"],
index: 0
};
render() {
const { meditations, index } = this.state;
return (
<View style={{flex: 1}}>
<Animatable.Text
key={index}
animation={'fadeIn'}
iterationCount={2}
direction="alternate"
duration={2000}
onAnimationEnd={() => {
if (this.state.index < this.state.meditations.length - 1) {
this.setState({ index: this.state.index + 1});
}
}}
style={{
position: "absolute",
left: 0, right: 0,
bottom: 40
}}>
{meditations[index]}
</Animatable.Text>
</View>
);
}
}
export default VideoScreen;
The map function executes all at once so basically you are rendering/returning all 3 items at the same time. I understand that your issue is that the animation is working tho.
If what you want is to show one text, then the other and so on I suggest iterating the index of your text array instead of using the map function.
Something like:
Execute Animation
Increase Index
Index = 0 if you are at the end of the array.
In a loop. Check setInterval, it might help you.
For the function components:-
we can use the above-metioned solutions. I am writing a function hopefully it will help you display a looping text with the animation
We will use this package for the animation https://github.com/oblador/react-native-animatable.
import {StyleSheet} from 'react-native';
import React, {useState} from 'react';
import * as Animatable from 'react-native-animatable';
const UserMessage = () => {
const [index, setIndex] = useState(0);
const meditations = [
'Take a deep breath',
'embrace this feeling',
'breath deeply',
];
return (
<Animatable.Text
key={index}
animation={'fadeIn'}
iterationCount={2}
direction="alternate"
duration={2000}
onAnimationEnd={() => {
if (index < meditations.length - 1) {
setIndex(index + 1);
} else {
setIndex(0);
}
}}
style={styles.messageStyle}>
{meditations[index]}
</Animatable.Text>
);
};
export default UserMessage;
const styles = StyleSheet.create({
messageStyle: {
textAlign: 'center',
fontSize: 18,
fontWeight: '500',
width: '80%',
color: '#1C1C1C',
marginBottom: 20,
minHeight: 50,
alignSelf: 'center',
},
});
I'm having a problem with React Native and Parse JS SDK.
And I'm using ParseReact
I have built a login, sign up and a main view, Sign up and log in works well but after i logged in -> directed to main view and when I refresh the app (CMD+R) in my simulator, it brings me back to the login view again, i should be brought to Main view.
As you can see I have set a state for initialComponent:
this.state = {
InitialComponent : ((!currentUser) ? LoginView : MainView)
};
This allows my navigator to check for currentUser is null then load LoginView as initial component, else set Main View(user logged in)
'use strict';
var React = require('react-native');
var MainView = require('./MainView');
var LoginView = require('./LoginView');
var Parse = require('parse').Parse;
var ParseReact = require('parse-react');
Parse.initialize("mykey", "mykey");
var {
AppRegistry,
StyleSheet,
Text,
View,
TextInput,
TouchableHighlight,
Navigator,
Component
} = React;
class MyApp extends Component {
constructor(props) {
super(props);
var currentUser = Parse.User.current();
console.log('Current User:' + currentUser);
this.state = {
InitialComponent : ((!currentUser) ? LoginView : MainView)
};
}
render() {
return (
<Navigator
initialRoute={{
name : 'StatusList',
component: this.state.InitialComponent
}}
configureScene = {() =>{
return Navigator.SceneConfigs.FloatFromRight;
}}
renderScene={(route, navigator) =>{
if(route.component) {
return React.createElement(route.component, {navigator});
}
}}/>
);
}
}
AppRegistry.registerComponent('MyApp', function() { return MyApp });
In my Xcode console, i kept getting current user is null after each refresh even though i have previously logged in. On my parse app, I can see new session has been created.
In my LoginView.
'use strict';
var React = require('react-native');
var SignUp = require('./SignUp');
var MainView = require('./MainView');
var {
AppRegistry,
StyleSheet,
Text,
View,
TextInput,
TouchableHighlight,
Navigator,
AlertIOS,
Component
} = React;
var styles = StyleSheet.create({
container : {
flex: 1,
padding: 15,
marginTop: 30,
backgroundColor: '#0179D5',
},
text: {
color: '#000000',
fontSize: 30,
margin: 100
},
headingText: {
color: '#fff',
fontSize: 40,
fontWeight: '100',
alignSelf: 'center',
marginBottom: 20,
letterSpacing: 3
},
textBox: {
color: 'white',
backgroundColor: '#4BB0FC',
borderRadius: 5,
borderColor: 'transparent',
padding:10,
height:40,
borderWidth: 1,
marginBottom: 15,
},
greenBtn: {
height: 36,
padding: 10,
borderRadius: 5,
backgroundColor: '#2EA927',
justifyContent: 'center'
},
signUpButton: {
marginTop: 10,
height: 36,
padding: 10,
borderRadius: 5,
backgroundColor: '#FF5500',
justifyContent: 'center'
},
btnText: {
color : '#fff',
fontSize: 15,
alignSelf: 'center'
},
buttonText: {
fontSize: 18,
color: 'white',
alignSelf: 'center'
},
loginForm : {
flex:1,
marginTop:100
}
});
class LoginView extends Component {
constructor(props) {
super(props);
this.state = {
username: '',
password: ''
};
}
checkLogin() {
var success = true;
var state = this.state;
for(var key in state){
if(state[key].length <= 0){
success = false;
}
}
if(success) {
this._doLogin();
} else {
//show alert
AlertIOS.alert('Error','Please complete all fields',
[{text: 'Okay', onPress: () => console.log('')}]
);
}
}
goMainView() {
this.props.navigator.push({
title: "Home",
component: MainView
});
}
goSignUp() {
this.props.navigator.push({
title: "Sign Up",
component: SignUp
});
}
_doLogin() {
var parent = this;
Parse.User.logIn(this.state.username, this.state.password, {
success: function(user) {
parent.goMainView();
},
error: function(user, error) {
AlertIOS.alert('Login Error', error.message,
[{text: 'Okay', onPress: () => console.log('')}]
);
}
});
}
onUsernameChanged(event) {
this.setState({ username : event.nativeEvent.text });
}
onPasswordChanged(event) {
this.setState({ password : event.nativeEvent.text });
}
render() {
return(
<View style={styles.container}>
<View style={styles.loginForm}>
<Text style={styles.headingText}>
MyStatus
</Text>
<TextInput style={styles.textBox}
placeholder='Username'
onChange={this.onUsernameChanged.bind(this)}
placeholderTextColor='#fff'
autoCorrect={false}
>
</TextInput>
<TextInput style={styles.textBox}
placeholder='Password'
onChange={this.onPasswordChanged.bind(this)}
placeholderTextColor='#fff'
password={true}
>
</TextInput>
<TouchableHighlight style={styles.greenBtn}
underlayColor='#33B02C'
onPress={this.checkLogin.bind(this)}>
<Text style={styles.btnText}>Login</Text>
</TouchableHighlight>
<TouchableHighlight style={styles.signUpButton}
underlayColor='#D54700'
onPress={this.goSignUp.bind(this)}>
<Text style={styles.btnText}>Sign Up</Text>
</TouchableHighlight>
</View>
</View>
);
}
}
module.exports = LoginView;
Am I doing it the wrong way? Kindly advice. Or is it something wrong with parse localstorage/session?
Because React Native only provides asynchronous storage, we're forced to make Parse.User.current() an asynchronous call that returns a Promise. Since you're already using Parse+React, it's really easy to handle this. Your component that changes based upon whether the user is logged in or not (MyApp) should subscribe to ParseReact.currentUser. This stays synchronized with the current user, and allows your component to automatically update the moment the user logs in or out. For a demo of this, look at the AnyBudget demo in the GitHub repo:
In react-native...
import Parse from 'parse/react-native';
Parse.User.currentAsync()
.then((currentUser)=>{
if (currentUser) {
Alert.alert('', JSON.stringify(currentUser))
}
});
At first in my case I use ParseReact.Mutation that work perfectly.
After that I got a requirement that some field need to be unique, so I use Parse.Cloud to solve problem.
// For normally situaltion is work fine.
ParseReact.Mutation.Set(this.data.myObject, {
normal_field: this.state.value
}).dispatch();
// But if I use Parse.Cloud. Is there any solution to sync data to ParseReact.currentUser?
Parse.Cloud.run('setUniqueField', {
userID: this.data.user.id.objectId,
uniqueField: this.state.value,
}, {
success: function (response) {
// It doesn't work!
// ParseReact.currentUser.update();
this.props.navigator.pop();
}.bind(this),
error: function (error) {
console.log('[Parse Cloud Error]: ', error);
}
});
#Andrew's solution (alone) didn't work for me. In case anyone else is stuck, here's what was happening: subscribing to Parse.User.current() didn't work, and this.data.user was always null.
To fix this, I had to change:
var Parse = require('parse').Parse;
var ParseReact = require('parse-react');
to
var Parse = require('parse/react-native').Parse;
var ParseReact = require('parse-react/react-native');
then everything started working beautifully. It was loading the browser version of the parse modules until I forced it to load the react-native versions.