Full-width image with aspect ratio in React Native - image

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.

Related

I'm trying to Use AppLoading to render home screen components but cant get the splash screen to stay until everything is loaded

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);
}
}

Passing props from component to TabNavigator - React-Navigation

I'm currently working on a React Native project for Android. Now I created a TabNavigator using React Navigations createMaterialTopTabNavigator, which displays two tabs:
It would be nice to use this TabNavigator as a reusable component. Therefore, I'm trying to pass props from the component that is calling the navigator. Unfortunately, I can't figure out how to pass the props correctly.
This is the component that calls the TabNavigator (named TwoTabsHorizontal here):
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {withNavigation} from 'react-navigation';
import Background from '../../atoms/Background/Background';
import TwoTabsHorizontal from '../../molecules/TwoTabsHorizontal/TwoTabsHorizontal';
class Main extends React.Component {
render() {
return (
<View style={styles.view}>
<Background background={true} title="Find Dogs" />
<TwoTabsHorizontal
headingLeftTab="criteria"
headingRightTab="names"
/>
</View>
);
}
}
const styles = StyleSheet.create({
view: {
backgroundColor: '#151414',
height: '100%',
},
});
export default withNavigation(Main);
And this is the TabNavigator TwoTabsHorizontal:
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {createMaterialTopTabNavigator} from 'react-navigation-tabs';
import {createAppContainer} from 'react-navigation';
import TestScreen1 from '../../../screens/TestScreen1';
import TestScreen2 from '../../../screens/TestScreen2';
const TabNavigator = ({headingLeftTab}) =>
createMaterialTopTabNavigator(
{
One: {
screen: TestScreen1,
navigationOptions: {
tabBarLabel: {headingLeftTab},
},
},
Two: {
screen: TestScreen2,
navigationOptions: {
tabBarLabel: 'Names',
},
},
},
{
},
);
export const TwoTabsHorizontal = createAppContainer(TabNavigator);
As you can see, I try to pass the prop headingLeftTab from Main down to TwoTabsHorizontal to use it as label in navigationOptions. It gives the following error:
I already tried the approach that is suggested here.

How to set the same background image for all components in React Native?

Good morning, everyone. I start in native react and I would like to put the same image in my mobile application, I make for each screes (page) a which makes my image load each time I change screens, what I want to do is put the same image and load it for all the components so the image loads only once for all the screens
what I did was create a background.js component that contains a that I export and import into other screens, but it doesn't work
Here's what I did in background.js
import React from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
KeyboardAvoidingView,
AsyncStorage,
TouchableOpacity,
Dimensions,
ImageBackground, ScrollView, StatusBar
} from 'react-native';
import Service from '../../service/base';
import bgImage from '../../assets/ToG.jpg'
import {Header} from "react-native-elements";
const service = new Service();
const { width : WIDTH } = Dimensions.get('window');
export default class Background extends React.Component {
render() {
return (
<View style={styles.f}>
<ImageBackground source={bgImage} style={styles.bgImage}>
</ImageBackground>
</View>
);
}
}
and then I import it into the other screens
import React from 'react';
import {
StyleSheet,
Text,
View,
TextInput,
KeyboardAvoidingView,
AsyncStorage,
TouchableOpacity,
Dimensions,
ImageBackground, ScrollView, StatusBar
} from 'react-native';
import bgImage from '../../assets/ToG.jpg'
import Background from './Background'
import {
Image,
Header,
Button,
} from "react-native-elements";
const {width : WIDTH} = Dimensions.get('window')
export default class Classement extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<Background>
<ScrollView>
<Header leftComponent={{
icon: 'menu',
size: 30,
color: '#fff',
fontWeight: 'bold',
onPress :() => this.props.navigation.openDrawer(),
}}
centerComponent={{ text: 'TOG', style: { color: '#fff' } }}
backgroundColor= "transparent">
</Header>
<StatusBar
barStyle="light-content"
animated={true}
backgroundColor="#6a51ae"/>
<KeyboardAvoidingView behavior='padding' style={styles.wrapper}>
<View style={styles.container}>
<Text style={styles.header}>Matchs</Text>
<Text style={styles.text}>
Bah y a rien à montrer p'tit chat, t'attends quoi pour rentrer une feuille de match ? </Text>
</View>
</KeyboardAvoidingView>
</ScrollView>
</Background>
);
}
}
ça ne marche pas
the result is a screen, the image loads but the component itself no longer loads
You will need to add {this.props.children} in your Background component to render what you are passing in the Background component from the Classement component.
You can look at this question, which they are doing something similar What is {this.props.children} and when you should use it?. The documentation for that is here https://reactjs.org/docs/composition-vs-inheritance.html#containment.
Hope it can help you!

react native navigation - componentDidMount() fired twice

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.

React Native Animating List of texts

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',
},
});

Resources