Invariant Violation Text string must be rendered within a <Text> component - javascript

I'm really sorry to bother you, but I try to learn React-Native. I'm trying to make a weather app and I have this error :
Text strings must be rendered within a <Text> component.
- node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:4137:8 in <anonymous>
- node_modules\react-native\Libraries\Renderer\implementations\ReactNativeRenderer-dev.js:4134:2 in createTextInstance
- ... 9 more stack frames from framework internal
Here's are my two files of code:
weather-card.js
import React, {Component} from "react";
import {Animated, View, Text, PanResponder, Image} from "react-native";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
const CARD_INITIAL_POSITION_Y = hp("80%");
const CARD_INITIAL_POSITION_X = wp("5%");
const TRESHOLD_TO_TOP = hp("75%");
const TRESHOLD_TO_BOTTOM = hp("70%");
const CARD_OPEN_POSITION = hp("45%");
const MAX_DRAG_ZONE_WHEN_OPEN = hp("65%");
const ICON_URL = "http://openweathermap.org/img/w/";
class WeatherCard extends Component {
state = { panResponder : undefined, isOpen: false};
componentDidMount() {
this.position = new Animated.ValueXY();
this.position.setValue({x:CARD_INITIAL_POSITION_X, y: CARD_INITIAL_POSITION_Y});
const panResponder = PanResponder.create({
onStartShouldSetPanResponder:() => true,
onPanResponderMove:(e,gesture) => {
if(!(this.state.isOpen && gesture.y0 > MAX_DRAG_ZONE_WHEN_OPEN)) {
this.position.setValue({
x:CARD_INITIAL_POSITION_X,
y: gesture.moveY
});
}
},
onPanResponderRelease:(e, gesture) => {
if (!this.state.isOpen){
if(gesture.moveY <= TRESHOLD_TO_TOP) {
this.setOpenPosition(() => this.setState({isOpen: true}));
} else {
this.resetPosition();
}
} else {
if(gesture.moveY <= TRESHOLD_TO_BOTTOM){
this.setOpenPosition()
} else {
if(gesture.y0 < MAX_DRAG_ZONE_WHEN_OPEN){
this.resetPosition(() => this.setState({isOpen: false}))
}
}
}
}
});
this.setState({panResponder})
}
setOpenPosition = (done) => {
Animated.spring(this.position, {
toValue: {x: CARD_INITIAL_POSITION_X, y : CARD_OPEN_POSITION}
}).start(() => done && done());
};
resetPosition = (done) => {
Animated.spring(this.position, {
toValue: {x: CARD_INITIAL_POSITION_X, y : CARD_INITIAL_POSITION_Y}
}).start(() => done && done())
};
getCardStyle() {
return {
width: wp("90%"),
height: hp("110%"),
borderRadius: 10,
zIndex: 2,
backgroundColor: "white",
elevation: 1,
shadowColor: "black",
shadowOpacity: 0.2,
shadowOffset: { height: 2, width: 2 },
position: "absolute",
left: CARD_INITIAL_POSITION_X,
padding: hp("2%"),
...this.position.getLayout()
};
}
renderHeader(){
return (
<View
style={{
justifyContent: "center",
alignItems: "center"
}}
>
<Text style={{ fontSize: 30, marginBottom: hp("1%") }}>
{this.props.currentWeather.name}
</Text>
<View style={{ flexDirection: "row" }}>
<Text style={{ marginTop: hp("1%"), fontSize: 35 }}>
{this.props.currentWeather.main.temp}
</Text>
<Image
style={{ height: 60, width: 60 }}
source={{
uri: `${ICON_URL}${this.props.currentWeather.weather[0].icon}.png`
}}
/>
</View>
</View>
)
}
render() {
return this.state.panResponder ? <Animated.View {...this.state.panResponder.panHandlers} style={this.getCardStyle()}> {this.renderHeader()} </Animated.View> : <View />;
}
}
export default WeatherCard;
And search-screen.js
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import MapView from 'react-native-maps';
import {SearchBar} from "react-native-elements";
import {
widthPercentageToDP as wp,
heightPercentageToDP as hp
} from "react-native-responsive-screen";
import {connect} from "react-redux";
import {getCurrentWeatherByCity} from "../actions/index";
import WeatherCard from "../components/weather-card";
const DEFAULT_COORD = {
lat: 48.859268,
lng: 2.347060
};
class SearchScreen extends React.Component {
state={search: ""};
updateSearch = search => {
this.setState({search})
};
submitSearch = () => {
this.props.getCurrentWeatherByCity(this.state.search);
console.log(this.state.search)
};
render() {
console.log(this.props.currentWeather);
return (
<View style={styles.container}>
<MapView style={{flex : 1}}
region={{latitude : this.props.currentWeather ? this.props.currentWeather.coord.lat : DEFAULT_COORD.lat, longitude : this.props.currentWeather ? this.props.currentWeather.coord.lon : DEFAULT_COORD.lng, latitudeDelta: 0.2000, longitudeDelta: 0.1000}}
scrollEnabled={false}
liteMode={true} />
{this.props.currentWeather && <WeatherCard currentWeather={this.props.currentWeather} />}
<SearchBar
lightTheme
onChangeText={this.updateSearch}
value={this.state.search}
onSubmitEditing={this.submitSearch}
containerStyle={{
position: "absolute",
bottom: hp("50%"),
left: wp("5%"),
width: wp("90%")
}}/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1
},
});
const mapStateToProps = (store) => {
return {
currentWeather : store.weather.currentWeather
}
};
const mapDispatchToProps = {
getCurrentWeatherByCity
};
export default connect(mapStateToProps, mapDispatchToProps)(SearchScreen)
I was asking myself if the problem could be :
{this.props.currentWeather && <WeatherCard currentWeather={this.props.currentWeather} />}
Because it's not inside a Text component...
If you can help, it could be really great :D
If you need other resources, do not hesitate!
Thanks in advance :)

It might be because of this try this:
change
{this.props.currentWeather.name}
to
{this.props.currentWeather.name != undefined ? this.props.currentWeather.name : null}
Similarly do the above thing for {this.props.currentWeather.main.temp}
Hope this helps!

I would like to post my answer if someone has the same problem. The error says that a text string is "outside" a component. However it wasn't the case. It was indentation's problem. So don't forget to pay attention about that :D Expo can be mean !

Related

React Native - Trying change 2 View heights with GestureHandler

I am trying to create Height adjustable Views with React Native for an app I am building. I keep getting stuck on this one aspect. I am trying to create two stacked Views, with a line inbetween them so they are height adjustable when dragging the line up or down, adjusting content in it as well. Image below is a representation of what I am trying to make. "Home Option 2" is default state, "Home Option 1.3 is when the slider is dragged down, and "Home Option 1.2" is opposite - slider dragged up.
With an app bar at the bottom. (I dont have it made yet)
Any thoughts or help is appreciated!
Here is my code for App.tsx
import * as React from 'react';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import { StatusBar } from 'expo-status-bar';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import BottomSheet, { BottomSheetRefProps } from './components/BottomSheet';
import { useCallback, useRef } from 'react';
import MapView, { Marker, Geojson } from "react-native-maps";
import { PROVIDER_GOOGLE } from "react-native-maps";
export default function App() {
const ref = useRef<BottomSheetRefProps>(null);
const [topViewHeight, setTopViewHeight] = React.useState(0);
const onPress = useCallback(() => {
const isActive = ref?.current?.isActive();
if (isActive) {
ref?.current?.scrollTo(0);
} else {
ref?.current?.scrollTo(-200);
}
}, []);
return (
<GestureHandlerRootView style={{ flex: 1 }}>
<View style={styles.mapViewContainer}>
<MapView
provider={PROVIDER_GOOGLE}
showsUserLocation={true}
style={styles.mapView}
initialRegion={{
latitude: 00.00 ,
longitude: -00.00 ,
latitudeDelta: 00.00 ,
longitudeDelta: 00.00 ,
}}
>
<Marker coordinate={{ latitude: 00.00, longitude: 00.00 }} />
</MapView>
</View>
<View style={styles.container}>
<StatusBar style="light" />
<TouchableOpacity style={styles.button} onPress={onPress} />
<BottomSheet ref={ref} {...{setTopViewHeight, topViewHeight}}>
<View style={{ flex: 1, backgroundColor: 'orange' }} />
</BottomSheet>
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#111',
alignItems: 'center',
justifyContent: 'center',
},
button: {
height: 50,
borderRadius: 25,
aspectRatio: 1,
backgroundColor: 'white',
opacity: 0.6,
},
mapViewContainer: {
height: "50%",
width: "95%",
overflow: "hidden",
background: "transparent",
borderRadius: 13,
},
mapView: {
height: "100%",
width: "100%",
},
});
Code for BottomSheet.tsx (Which i was using as a reference for the ideal UX)
import { Dimensions, StyleSheet, Text, View } from 'react-native';
import React, { useCallback, useEffect, useImperativeHandle } from 'react';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, {
Extrapolate,
interpolate,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from 'react-native-reanimated';
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
const TOP_VIEW_HEIGHT = 50;
const VIEW_RESIZE = 2.5;
const MAX_TRANSLATE_Y = -SCREEN_HEIGHT / VIEW_RESIZE;
type BottomSheetProps = {
children?: React.ReactNode;
setTopViewHeight: (height: number) => void;
topViewHeight: number;
};
export type BottomSheetRefProps = {
scrollTo: (destination: number) => void;
isActive: () => boolean;
};
const BottomSheet = React.forwardRef<BottomSheetRefProps, BottomSheetProps>(
({ children }, ref) => {
const translateY = useSharedValue(0);
const active = useSharedValue(false);
const scrollTo = useCallback((destination: number) => {
'worklet';
active.value = destination !== 0;
translateY.value = withSpring(destination, { damping: 50 });
}, []);
const isActive = useCallback(() => {
return active.value;
}, []);
useImperativeHandle(ref, () => ({ scrollTo, isActive }), [
scrollTo,
isActive,
]);
const context = useSharedValue({ y: 0 });
const gesture = Gesture.Pan()
.onStart(() => {
context.value = { y: translateY.value };
})
.onUpdate((event) => {
translateY.value = event.translationY + context.value.y;
translateY.value = Math.max(translateY.value, MAX_TRANSLATE_Y);
console.log(translateY.value);
})
.onEnd(() => {
if (translateY.value > -SCREEN_HEIGHT / 3) {
scrollTo(0);
} else if (translateY.value < -SCREEN_HEIGHT / 1.5) {
scrollTo(MAX_TRANSLATE_Y);
}
console.log('end: ' + translateY.value)
});
const rBottomSheetStyle = useAnimatedStyle(() => {
const borderRadius = interpolate(
translateY.value,
[MAX_TRANSLATE_Y + 50, MAX_TRANSLATE_Y],
[25, 5],
Extrapolate.CLAMP
);
return {
borderRadius,
transform: [{ translateY: translateY.value }],
maxHeight: 500,
};
});
return (
<GestureDetector gesture={gesture}>
<Animated.View style={[styles.bottomSheetContainer, rBottomSheetStyle] }>
<View style={styles.line} />
{children}
</Animated.View>
</GestureDetector>
);
}
);
const styles = StyleSheet.create({
bottomSheetContainer: {
minHeight: SCREEN_HEIGHT - 50,
width: '100%',
backgroundColor: 'white',
position: 'relative',
top: SCREEN_HEIGHT - 500,
borderRadius: 25,
},
line: {
width: 75,
height: 4,
backgroundColor: 'grey',
alignSelf: 'center',
marginVertical: 15,
borderRadius: 2,
},
});
export default BottomSheet;
The Bar component will have the GestureHandler tied to it. Interpolate yTranslation into a value between 0 and 1. The Bar component's SharedValue is passed as prop so that other components in its parent contain utilize it:
import {
StyleSheet,
ViewStyle,
Dimensions,
View,
useWindowDimensions,
} from 'react-native';
import Animated, {
SharedValue,
useAnimatedStyle,
interpolate,
withTiming,
} from 'react-native-reanimated';
import { GestureDetector, Gesture } from 'react-native-gesture-handler';
type Props = {
anim: SharedValue<number>;
style?: ViewStyle;
};
const snapPoints = [0.2, 0.5, 0.8];
export default function Bar({ anim, style }: Props) {
const { height } = useWindowDimensions();
const gesture = Gesture.Pan()
.onUpdate((e) => {
// interpolate yTranslation to a value that snapPoints can work with
anim.value = interpolate(
e.translationY,
[-height * 0.5, height * 0.5],
[0, 1]
);
})
// snap to nearest point
.onEnd(() => {
const snapPoint = snapPoints.reduce((prev, curr) => {
const prevDist = Math.abs(prev - anim.value);
const currDist = Math.abs(curr - anim.value);
return prevDist < currDist ? prev : curr;
}, snapPoints[0]);
console.log('snapping to ', snapPoint);
// animate snapping to snapPoint
anim.value = withTiming(snapPoint);
});
return (
<GestureDetector gesture={gesture}>
<View style={styles.barContainer}>
<View style={styles.bar} />
</View>
</GestureDetector>
);
}
const styles = StyleSheet.create({
barContainer: {
backgroundColor: 'transparent',
width: '100%',
//padding to make bar easier to press
padding: 10,
justifyContent: 'center',
},
bar: {
backgroundColor: '#c4c4c4',
width: '80%',
height: 7,
alignSelf: 'center',
borderRadius: 25,
},
});
Now that translationY is a percentage it can be used to determine the amount of flex each view have:
import React from 'react';
import {
View,
StyleSheet,
} from 'react-native';
import Constants from 'expo-constants';
import Animated, {
useSharedValue,
useAnimatedStyle,
} from 'react-native-reanimated';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
import SliderBar from './SliderBar';
import View1 from './View1';
import View2 from './View2';
import { footerHeight, ScreenWidth, ScreenHeight, MAX_FLEX } from './Constants';
export default function App() {
const barValue = useSharedValue(0.5);
const view1Style = useAnimatedStyle(() => {
return {
flex: barValue.value * MAX_FLEX,
};
});
const view2Style = useAnimatedStyle(() => {
return {
flex: Math.abs(barValue.value - 1) * MAX_FLEX,
};
});
return (
<GestureHandlerRootView
style={{ width: ScreenWidth, height: ScreenHeight }}>
<View style={styles.container}>
<Animated.View style={[styles.viewStyle, view1Style]}>
<View1 />
</Animated.View>
<SliderBar anim={barValue} />
<Animated.View style={[styles.viewStyle, view2Style]}>
<View2 />
</Animated.View>
<View style={styles.footer} />
</View>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
padding: 8,
margin: 5,
},
viewStyle: {
backgroundColor: '#c4c4c4',
flex: 1,
marginVertical: 10,
borderRadius: 10,
},
footer: {
backgroundColor: '#6f6f6f',
height: footerHeight,
borderRadius: 10,
},
});
Demo

React Native: Touchable Opacity element is clickable on iOS but not Android

I'm working on a React Native app with a typeahead component. The typeahead displays options that overlay other content on the route (see right image below). When a user clicks one of those options, an onPress listener runs a function:
This all works just fine on iOS. On Android though, the onPress event is never received. Even more strangely, when I try to click on an option lower in the list (like Boston, MA, USA), the onPress event is received by the card below the pressed option (Djerba).
Does anyone know what might cause this behavior? I'd be super grateful for any insights others can offer on this query.
Here's the code for the Explore view and the typeahead components.
Explore.js
import React from 'react'
import { connect } from 'react-redux'
import { Text, View, ScrollView, TouchableOpacity } from 'react-native'
import { gradients, sizing } from '../../style'
import { LinearGradient } from 'expo-linear-gradient'
import { MountainHero } from '../Heros'
import { CardRow } from '../Card'
import Loading from '../Loading'
import { setExploreSearch, onExploreTypeaheadClick } from '../../actions/locations'
import { Typeahead } from '../Typeahead'
const styles = {
container: {
flex: 1,
flexDirection: 'column',
},
scrollView: {
paddingBottom: sizing.margin,
},
loadingContainer: {
position: 'absolute',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
zIndex: 100,
elevation: 100,
top: 53,
width: '100%',
},
typeahead: {
margin: sizing.margin,
marginBottom: 0,
width: sizing.screen.width - (2*sizing.margin),
zIndex: 100,
elevation: 100,
}
}
const Explore = props => {
const { authenticated: a, spotlight, loading } = props;
let r = (a.recommendedLocations || []);
if (!r || !spotlight) return null;
// remove spotlight locations from the recommended locations
const ids = spotlight.map(i => i.guid);
const recommended = r.filter(i => ids.indexOf(i.guid) == -1);
return (
<LinearGradient style={styles.container} colors={gradients.teal}>
<ScrollView contentContainerStyle={styles.scrollView}>
{loading && (
<View style={styles.loadingContainer}>
<Loading />
</View>
)}
<MountainHero text='Explore' />
<Typeahead
style={styles.typeahead}
placeholder='Search Cities'
value={props.exploreSearch}
onChange={props.setExploreSearch}
vals={props.exploreTypeahead}
valKey={'place_id'}
onTypeaheadClick={props.onExploreTypeaheadClick}
/>
<CardRow
text='Explore Places'
cards={recommended}
type='location' />
<CardRow
text='In the Spotlight'
cards={spotlight}
type='location' />
</ScrollView>
</LinearGradient>
)
}
const mapStateToProps = state => ({
authenticated: state.users.authenticated,
spotlight: state.locations.spotlight,
exploreSearch: state.locations.exploreSearch,
exploreTypeahead: state.locations.exploreTypeahead,
loading: state.locations.loading,
})
const mapDispatchToProps = dispatch => ({
setExploreSearch: s => dispatch(setExploreSearch(s)),
onExploreTypeaheadClick: val => dispatch(onExploreTypeaheadClick(val)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Explore)
Typeahead.js
import React from 'react'
import { Text, View, TouchableOpacity } from 'react-native'
import { sizing, GradientInput } from '../style'
const styles = {
container: {
position: 'absolute',
zIndex: 100,
elevation: 100,
height: 400,
width: '100%',
},
input: {
width: '100%',
borderRadius: 0,
},
typeaheadContainer: {
position: 'absolute',
zIndex: 100,
elevation: 100,
top: 55,
width: '100%',
},
typeaheadRow: {
padding: 10,
paddingTop: 12,
paddingBottom: 12,
borderWidth: 1,
borderColor: '#eeeeee',
backgroundColor: '#ffffff',
marginBottom: -1,
},
typeaheadRowText: {
fontSize: 15,
fontFamily: 'open-sans',
lineHeight: 20,
backgroundColor: '#ffffff',
},
}
export const Typeahead = props => {
return (
<View style={[props.container, props.style]}>
<GradientInput style={styles.input}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange} />
<TypeaheadList vals={props.vals}
valKey={props.valKey}
onTypeaheadClick={props.onTypeaheadClick} />
</View>
)
}
export const TypeaheadList = props => {
if (!props.vals) return null;
return (
<View style={styles.typeaheadContainer}>
{props.vals.map(i => {
let text = i.text;
if (text.length > 31) text = text.substring(0,31) + '...';
return (
<TouchableOpacity activeOpacity={0.5} key={i[props.valKey]}
style={styles.typeaheadRow}
onPress={() => props.onTypeaheadClick(i[props.valKey])}>
<Text numberOfLines={1} style={styles.typeaheadRowText}>{text}</Text>
</TouchableOpacity>
)
})}
</View>
)
}
export default Typeahead
Try to move Typeahead component below all CardRow components and set position:absolute for Typeahead. Probably on android - the latest view shadow all views before (I am not sure, but I think you have to try it for next discovering issue).
You should also remove position: absolute from all but one component. Working code:
Explore.js
import React from 'react'
import { connect } from 'react-redux'
import { Text, View, ScrollView, TouchableOpacity } from 'react-native'
import { gradients, sizing } from '../../style'
import { LinearGradient } from 'expo-linear-gradient'
import { MountainHero } from '../Heros'
import { CardRow } from '../Card'
import Loading from '../Loading'
import { setExploreSearch, onExploreTypeaheadClick } from '../../actions/locations'
import { Typeahead } from '../Typeahead'
const styles = {
container: {
flex: 1,
flexDirection: 'column',
},
scrollView: {
paddingBottom: sizing.margin,
},
loadingContainer: {
position: 'absolute',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
zIndex: 1,
elevation: 1,
top: 53,
width: '100%',
},
topCardRow: {
paddingTop: sizing.margin + sizing.gradientInput.height,
},
typeahead: {
margin: sizing.margin,
marginBottom: 0,
width: sizing.screen.width - (2*sizing.margin),
zIndex: 1,
elevation: 1,
position: 'absolute',
top: sizing.mountainHero.height,
left: 0,
}
}
const Explore = props => {
const { authenticated: a, spotlight, loading } = props;
let r = (a.recommendedLocations || []);
if (!r || !spotlight) return null;
// remove spotlight locations from the recommended locations
const ids = spotlight.map(i => i.guid);
const recommended = r.filter(i => ids.indexOf(i.guid) == -1);
return (
<LinearGradient style={styles.container} colors={gradients.teal}>
<ScrollView contentContainerStyle={styles.scrollView}>
{loading && (
<View style={styles.loadingContainer}>
<Loading />
</View>
)}
<MountainHero text='Explore' />
<CardRow
style={styles.topCardRow}
text='Explore Places'
cards={recommended}
type='location' />
<CardRow
text='In the Spotlight'
cards={spotlight}
type='location' />
<Typeahead
style={styles.typeahead}
placeholder='Search Cities'
value={props.exploreSearch}
onChange={props.setExploreSearch}
vals={props.exploreTypeahead}
valKey={'place_id'}
onTypeaheadClick={props.onExploreTypeaheadClick}
/>
</ScrollView>
</LinearGradient>
)
}
const mapStateToProps = state => ({
authenticated: state.users.authenticated,
spotlight: state.locations.spotlight,
exploreSearch: state.locations.exploreSearch,
exploreTypeahead: state.locations.exploreTypeahead,
loading: state.locations.loading,
})
const mapDispatchToProps = dispatch => ({
setExploreSearch: s => dispatch(setExploreSearch(s)),
onExploreTypeaheadClick: val => dispatch(onExploreTypeaheadClick(val)),
})
export default connect(mapStateToProps, mapDispatchToProps)(Explore)
Typeahead.js
import React from 'react'
import { Text, View, TouchableOpacity } from 'react-native'
import { sizing, GradientInput } from '../style'
const styles = {
container: {
zIndex: 1,
elevation: 1,
height: 400,
width: '100%',
},
input: {
width: '100%',
borderRadius: 0,
},
typeaheadContainer: {
zIndex: 1,
elevation: 1,
top: 0,
width: '100%',
},
typeaheadRow: {
padding: 10,
paddingTop: 12,
paddingBottom: 12,
borderWidth: 1,
borderColor: '#eeeeee',
backgroundColor: '#ffffff',
marginBottom: -1,
zIndex: 1,
elevation: 1,
},
typeaheadRowText: {
fontSize: 15,
fontFamily: 'open-sans',
lineHeight: 20,
backgroundColor: '#ffffff',
},
}
export const Typeahead = props => {
return (
<View style={[props.container, props.style]}>
<GradientInput style={styles.input}
placeholder={props.placeholder}
value={props.value}
onChange={props.onChange} />
<TypeaheadList vals={props.vals}
valKey={props.valKey}
onTypeaheadClick={props.onTypeaheadClick} />
</View>
)
}
export const TypeaheadList = props => {
if (!props.vals) return null;
return (
<View style={styles.typeaheadContainer}>
{props.vals.map(i => {
let text = i.text;
if (text.length > 31) text = text.substring(0,31) + '...';
return (
<TouchableOpacity activeOpacity={0.5} key={i[props.valKey]}
style={styles.typeaheadRow}
onPress={() => props.onTypeaheadClick(i[props.valKey])}>
<Text numberOfLines={1} style={styles.typeaheadRowText}>{text}</Text>
</TouchableOpacity>
)
})}
</View>
)
}
export default Typeahead

Trying to give a fade animation to my image carousel, React-Native

I want to animate this image carousel in reactNative, but have no idea how to start. Read the documentation about animations but still really stuck, have no idea how to incorporate it in. I tried it this way but keep getting a big fat error. Help!
import React from 'react';
import {StyleSheet, View, ScrollView, Dimensions, Image, Animated} from 'react-native'
const DEVICE_WIDTH = Dimensions.get('window').width;
class BackgroundCarousel extends React.Component {
scrollRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
opacity: new Animated.Value(0)
};
}
componentDidMount = () => {
Animated.timing(this.state.opacity , {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
setInterval(() => {
this.setState(
prev => ({ selectedIndex: prev.selectedIndex ===
this.props.images.length - 1 ? 0 : prev.selectedIndex +1 }),
() => {
this.scrollRef.current.scrollTo({
animated: true,
y: 0,
x: DEVICE_WIDTH * this.state.selectedIndex
});
}
);
}, 6000);
};
componentWillUnmount() {
clearInterval(this.setState);
}
render() {
const {images} = this.props
const {selectedIndex} = this.state
return (
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
},
this.props.style,
]}
/>
<View style= {{height: "100%", width: "100%"}}>
{this.props.children}
<ScrollView
horizontal
pagingEnabled
scrollEnabled={false}
ref={this.scrollRef}
>
{images.map(image => (
<Image
key={image}
source={image}
style={styles.backgroundImage}
/>
))}
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create ({
backgroundImage: {
height: '100%',
width: DEVICE_WIDTH,
}
});
export default BackgroundCarousel;
Any help would be appreciated. Don't know where I'm going wrong. Basically trying to add a fade effect when my background carousel changes from image to image.
I have fixed your code and removed all errors, copy-paste it in https://snack.expo.io/ and give it some time to load.
Note: I have removed this.props.images for website demo, please change in your real project.
Working fade carousal: https://snack.expo.io/#rajrohityadav/fade-carosal
But I have not implemented this using React Animation.
import React from 'react';
import {StyleSheet, View, ScrollView, Dimensions, Image, Animated} from 'react-native'
const DEVICE_WIDTH = Dimensions.get('window').width;
export default class BackgroundCarousel extends React.Component {
scrollRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
opacity: new Animated.Value(0)
};
}
componentDidMount = () => {
Animated.timing(this.state.opacity , {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
setInterval(() => {
this.setState(
prev => ({ selectedIndex: prev.selectedIndex ===
3 - 1 ? 0 : prev.selectedIndex +1 }),
() => {
this.scrollRef.current.scrollTo({
animated: true,
y: 0,
x: DEVICE_WIDTH * this.state.selectedIndex
});
}
);
}, 6000);
};
componentWillUnmount() {
clearInterval(this.setState);
}
render() {
const images =[
'https://image.shutterstock.com/image-vector/dragon-scream-vector-illustration-tshirt-260nw-1410107855.jpg','https://image.shutterstock.com/image-vector/dragon-head-vector-illustration-mascot-600w-1201914655.jpg',
'https://i.pinimg.com/474x/b7/1a/bb/b71abb6dd7678bbd14a1f56be5291747--dragon-illustration-samurai-tattoo.jpg']//this.props
const {selectedIndex} = this.state
return (
<>
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
},
this.props.style,
]}
/>
<View style= {{height: "100%", width: "100%"}}>
{this.props.children}
<ScrollView
horizontal
pagingEnabled
scrollEnabled={false}
ref={this.scrollRef}
>
{images.map(image => (
<Image
key={image}
source={image}
style={styles.backgroundImage}
/>
))}
</ScrollView>
</View>
</>
)
}
}
const styles = StyleSheet.create ({
backgroundImage: {
height: '100%',
width: DEVICE_WIDTH,
}
});
You can also use a simple & optimized library react-native-fadecarousel and use it like this:
import React from 'react'
import { View, StyleSheet, Image } from 'react-native';
import FadeCarousel from 'react-native-fadecarousel';
const FadeCarouselScreen = () => {
const images = [
'https://image.shutterstock.com/image-vector/dragon-scream-vector-illustration-tshirt-260nw-1410107855.jpg',
'https://image.shutterstock.com/image-vector/dragon-head-vector-illustration-mascot-600w-1201914655.jpg',
'https://i.pinimg.com/474x/b7/1a/bb/b71abb6dd7678bbd14a1f56be5291747--dragon-illustration-samurai-tattoo.jpg'
];
return <FadeCarousel
loop
fadeAnimationDuration={1000}
autoPlay={{enable: true , delay: 1000 }}>
{
images.map((image, index) => {
return <View key={`slide ${index}`} style={styles.slideItem}>
<Image style={styles.image} resizeMethod="resize" resizeMode="cover" source={{ uri: image }}/>
</View>
})
}
</FadeCarousel>
}
const styles = StyleSheet.create({
image: {
width: "100%",
height: 300
},
slideItem: {
width: "100%",
height: 300,
justifyContent: "center",
alignContent: "center"
}
})
export default FadeCarouselScreen

Pass data between two components in React Native

I have a component that encapsulates three components one of them is the DrawerLayoutAndroid. Now I want to pass the reference of the drawer from the drawer component to the main component and then pass it to the header component so I can trigger it from the header component.
I have no idea how to do that.
Here is the main component 'cases.js'
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';
import Header from '#components/header';
import BottomBar from '#components/bottom-bar';
import SideBar from '#components/drawer';
export default class Cases extends Component {
state = {
title : 'Cases'
}
change_text = () => {
this.setState({ title : 'Test' })
}
open_drawer = (ref) => {
ref.openDrawer();
}
close_drawer = (ref) => {
ref.closeDrawer();
}
render() {
return (
<SideBar style={ styles.container }>
<Header title={ this.state.title } change_text={ this.change_text } open={ this.state.side_bar } />
<View style={ styles.body }>
<Text style={ styles.text }> { this.state.name } </Text>
<TouchableOpacity onPress={ () => this.change_text() } style={ styles.button }>
<Text> Change State </Text>
</TouchableOpacity>
</View>
<BottomBar ref={ this.state.side_bar } />
</SideBar>
)
}
}
const styles = StyleSheet.create({
container : {
flex : 1,
flexDirection: 'column',
},
body : {
flex : 1,
backgroundColor: '#ccc',
justifyContent: 'center',
alignItems: 'center'
},
text : {
color : '#fff'
},
button : {
backgroundColor : '#eee',
paddingVertical: 5,
paddingHorizontal: 10
}
})
Here is my header:
import React, { Component } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, Image } from 'react-native';
import DrawerLayoutAndroid from 'DrawerLayoutAndroid';
import LinearGradient from 'react-native-linear-gradient';
export default class Header extends Component {
change_text = () => {
this.props.change_text();
}
open = ()
render () {
return (
<LinearGradient start={{x: 0, y: 0}} end={{x: 1, y: 0}} colors={['#a4d294', '#3ac6f3']} style={ styles.header }>
<TouchableOpacity onPress={ () => this.props.change_text() }>
<Image source={ require('#media/images/add.png') } style={ styles.add_button } />
</TouchableOpacity>
<Text style={ styles.title }> { this.props.title } </Text>
<TouchableOpacity onPress={ () => this.props.open() }>
<Image source={ require('#media/images/more.png') } style={ styles.more_button } />
</TouchableOpacity>
</LinearGradient>
)
}
}
const styles = StyleSheet.create({
header : {
height: 70,
backgroundColor: '#eee',
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center'
},
title : {
color : '#fff',
textAlign: 'center',
fontSize: 20,
letterSpacing: 3
},
add_button : {
width: 30,
height: 30,
marginHorizontal: 10
},
more_button : {
width: 30,
height: 30,
marginHorizontal: 10
}
})
And here is my drawer.js
import React, { Component } from 'react';
import { View, Text } from 'react-native';
import DrawerLayoutAndroid from 'DrawerLayoutAndroid';
import LinearGradient from 'react-native-linear-gradient';
export default class Drawer extends Component {
render () {
const NavigationMenu = (
<LinearGradient start={{x: 0, y: 0}} end={{x: 0, y: 1}} colors={['#a4d294', '#3ac6f3']} style={{ flex: 1 }}>
<View style={{ flex : 6, justifyContent: 'center' }}>
<Text>He There</Text>
</View>
</LinearGradient>
)
return (
<DrawerLayoutAndroid
drawerWidth={250}
drawerPosition={ DrawerLayoutAndroid.positions.Right }
renderNavigationView={ () => NavigationMenu }
ref={ (drawer) => this.props.ref = drawer }
>
{ this.props.children }
</DrawerLayoutAndroid>
)
}
}
I am going to answer your question by giving a simple example.
Lets take 2 components parent and child. You want to pass some message from parent to child and get a callback from child to parent when an event occurs in child.
export default class Parent extends React.Component<any, any> {
callback (paramFromChild) => {
// implement what to do when certain event occurs in child component
}
render () {
<View>
<Child message={"some text"} callbackFromChild={this.callback}/>
</View>
}
}
Child component
Interface childProps {
message: string
callbackFromChild(paramFromChild);
}
export default class Child extends React.Component<childProps, any> {
render () {
<View>
<Button title={this.props.message} onPress={this.props.callbackFromChild("some message from child")}/>
</View>
}
}
In this way you can communicate between different components using props.

How does "Animated.createAnimatedComponent" work?

In the Component ProgressBarAndroid, there are props indeterminable={Boolean} which show to a user an animation of what it's going on. I would like to do almost the same on ProgressViewIOS. So I tried to Animate it with Animated...
I saw on docs of Animated method called 'createAnimatedComponent' which they use to create Animated.View
I tried so to create another Animated (Native) Component but it doesn't work at all.
The animation should gradually raise fillValue to 20 % and continue with an original value from the media upload...
This is my Component
// ProgressBar.ios.js
// #flow
import { PropTypes } from 'react';
import Component from 'components/base/Component';
import { ProgressViewIOS, Animated } from 'react-native';
const AnimatedProgressViewIOS = Animated.createAnimatedComponent(ProgressViewIOS);
class ProgressBarIOS extends Component {
static propTypes = {
// Percentage (0 - 100)
fill: PropTypes.number.isRequired,
};
constructor(props, context: any) {
super(props, context);
this.state = {
fillValue: new Animated.Value(props.fill),
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.fill === 0) {
Animated.timing(this.state.fillValue, { toValue: 0.2, duration: 500 }).start();
} else if (nextProps.fill > 19) {
this.state.fillValue.setValue(nextProps.fill / 100);
}
}
shouldComponentUpdate(nextProps) {
return this.props.fill !== nextProps.fill;
}
render() {
return (
<AnimatedProgressViewIOS
style={{ alignSelf: 'stretch' }}
progress={this.state.fillValue} />
);
}
}
export default ProgressBarIOS;
EDIT: AnimatedComponent is used to modify style only. Props could be passed as animated value but remember it is not a number!
Animated.createAnimatedComponent can animate a number of different properties, however only some properties are supported using the native driver, fortunately it appears progress on ProgressViewIOS is one of them.
Here is a working implementation of an animated ProgressViewIOS.
import * as React from 'react';
import { View, SafeAreaView } from 'react-native';
import { ProgressViewIOS, Animated } from 'react-native';
const AnimatedProgressViewIOS = Animated.createAnimatedComponent(
ProgressViewIOS
);
export default function App() {
const value = React.useRef(new Animated.Value(0));
React.useEffect(() => {
Animated.loop(
Animated.timing(value.current, {
duration: 2000,
toValue: 1,
useNativeDriver: true,
})
).start();
}, []);
return (
<SafeAreaView>
<View style={{ padding: 20 }}>
<AnimatedProgressViewIOS
style={{ alignSelf: 'stretch' }}
progress={value.current}
/>
</View>
</SafeAreaView>
);
}
It's worth noting that ProgressViewIOS is now deprecated, but building your own progress view is very straight forward and requires just two Views with simple styling like this (expo snack):
import * as React from 'react';
import { View, SafeAreaView, StyleSheet, Button, Text } from 'react-native';
import { Animated } from 'react-native';
export default function App() {
const [progress, setProgress] = React.useState(() => Math.random());
return (
<SafeAreaView>
<View style={{ padding: 20 }}>
<AnimatedProgressView progress={progress} />
<Text style={{padding: 20, textAlign: 'center'}}>{Math.round(progress * 100)}%</Text>
<Button title="Animate" onPress={() => setProgress(Math.random())} />
</View>
</SafeAreaView>
);
}
function AnimatedProgressView({ progress, style }) {
const value = React.useRef(new Animated.Value(0));
const [width, setWidth] = React.useState(0);
React.useEffect(() => {
Animated.spring(value.current, { toValue: progress }).start();
}, [progress]);
return (
<View
style={[styles.track, style]}
onLayout={(event) => setWidth(event.nativeEvent.layout.width)}>
<Animated.View
style={[
styles.fill,
{
transform: [
{
translateX: value.current.interpolate({
inputRange: [0, 1],
outputRange: [-width, 0],
overflow: 'clamp',
}),
},
],
},
]}
/>
</View>
);
}
const styles = StyleSheet.create({
track: {
minHeight: 4,
borderRadius: 2,
overflow: 'hidden',
backgroundColor: '#ddd',
},
fill: {
...StyleSheet.absoluteFillObject,
backgroundColor: 'blue',
},
});

Categories