I am trying to set a global style variable from the componentWillMount() but I am not able to do that. When I am logging the variable it says undefined.
export default class Post extends React.Component {
constructor(props) {
super(props)
this.state = {
postImageUri: {
uri: this.props.postImageUri
}
}
}
componentWillMount() {
Image.getSize(this.props.postImageUri, (width, height) => {
...
styles.postImage.height = height
styles.postImage.width = Dimensions.get('window').width
console.log(styles.postImage.height) //Undefined
console.log(styles.postImage.width) //Undefined
...
});
}
render() {
return (
<View>
<Image source={this.state.postImageUri} style={styles.postImage}/>
</View>
}
}
val styles = StyleSheet.create({
postImage: {
height: 0,
width: 0
}
})
Instead of mutating the style, you should store the height in state:
state = {
height: null,
};
componentDidMount() {
Image.getSize(this.props.postImageUri, (width, height) => {
this.setState({ height });
});
}
Then use style composition to apply the height from the state:
<Image
source={this.state.postImageUri}
style={[styles.postImage, { height: this.state.height } ]}
/>
Your width computation is synchronous so you can set it directly in style:
const styles = StyleSheet.create({
postImage: {
width: Dimensions.get('window').width,
},
});
Store the style in state instead of storing in a global variable. This way the component will itself figure out whenever the state is changed and component will re-render.
You can change the state using this.setState({stateName: someValue});.
Related
My goal of this code:
Render some view elements with a loop
Inside the loop, set the state
On clicking the elements, update that value
Here is my code:
import React, {Component} from 'react';
import {View, Text, TouchableOpacity} from 'react-native';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
maths: {},
};
}
prepareMaths = function() {
var count = 5;
var updateMath = key => {
var stateMaths = this.state.maths;
stateMaths['position_' + key] = Math.random();
this.setState({maths: stateMaths}, () => {
console.log(this.state.maths);
});
};
var stateMaths = this.state.maths;
return [...Array(count)].map((options, key) => {
stateMaths['position_' + key] = Math.random();
this.setState({maths: stateMaths});
return (
<TouchableOpacity
key={key}
onPress={() => updateMath(key)}
style={{
height: 100,
width: 200,
marginRight: 20,
marginBottom: 10,
backgroundColor: 'green',
}}>
<Text>{this.state.maths['position_' + key]}</Text>
</TouchableOpacity>
);
});
};
render() {
return (
<View>
<View>{this.prepareMaths()}</View>
</View>
);
}
}
I'm getting this error with this code:
I'm very confused. Because if I remove setState... code inside the loop, it's showing random maths naturally. But how? Since I'm using this.state.maths['position_' + key] on render. I really don't know how that data is generating.
Please help.
Thanks in advance.
Issues
State mutations
var stateMaths = this.state.maths; // <-- state
stateMaths['position_' + key] = Math.random(); // <-- mutation!!
Updating state in render function causes rerender. render is a pure function with zero side-effects
return [...Array(count)].map((options, key) => {
stateMaths['position_' + key] = Math.random();
this.setState({maths: stateMaths}); // <-- trigger rerender
Solution
Factor prepareMaths and updateMath into standalone utility functions
Convert maths state to array
Use componentDidMount to initialize state
Use componentDidUpdate to log updated state
Move the JSX from prepareMaths to render function for mapping from state
Updated component
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
maths: [],
};
}
componentDidMount() {
this.prepareMaths();
}
componentDidUpdate() {
console.log(this.state.maths);
}
updateMath = (key) => {
this.setState((prevState) => ({
maths: prevState.maths.map((el, i) => (i === key ? Math.random() : el)),
}));
};
prepareMaths = function () {
const count = 5;
this.setState({ maths: [...Array(count)].map(Math.random) });
};
render() {
const { maths } = this.state;
return (
<View>
<View>
{maths.map((value, key) => (
<TouchableOpacity
key={key}
onPress={() => this.updateMath(key)}
style={{
height: 100,
width: 200,
marginRight: 20,
marginBottom: 10,
backgroundColor: 'green',
}}>
<Text>{value}</Text>
</TouchableOpacity>
))}
</View>
</View>
);
}
}
Expo Snack Demo
i am using react native code but some how code not working. please let me check how i can fix. i am getting value from cache and trying to return & show value.
i tried lot some how code not working if someone has any idea please let me know
import React, { memo } from 'react';
import { Text, View, StyleSheet, AsyncStorage } from 'react-native';
import { theme } from "../core/theme";
class Dashdata extends React.Component{
constructor(){
super();
this.getDataName = this.getDataName.bind(this);
this.state = {
displayname: ''
};
}
getDataName = () => {
const displayname = '';
console.log('getting value from cachedd');
const loginName = AsyncStorage.getItem('#kidssafety:displayname')
.then((result)=>{
console.log(result);
return (
<Text>{result}</Text>
)
});
}
render(){
return(
<View>
<Text style={styles.header}>Welcome Data {this.getDataName()}</Text>
</View>
)
}
}
const styles = StyleSheet.create({
header: {
fontSize: 22,
color: theme.colors.primary,
fontWeight: "bold",
paddingVertical: 14,
flex: 1,
marginTop: 100,
width: '100%',
textAlign: 'left'
}
});
export default memo(Dashdata);
AsyncStorage returns a promise. So you have to wait until it resolves.
Use Async/await to fix your problem.
getDataName = async () => {
const loginName = await AsyncStorage.getItem('#kidssafety:displayname')
this.setState({
displayname: loginName
})
}
Now you can display your values inside render
<Text style={styles.header}>Welcome Data {this.state.displayname}</Text>
Important
Since you are using as getDataName as an arrow function, you don't have to bind it as
this.getDataName = this.getDataName.bind(this)
Hope this helps you. Feel free for doubts.
AsyncStorage.getItem returns a promise and by the time it resolves and returns a value, your render would have moved to the next line. Ideally you should store the result to the state and use it when ready. Then your component will look like.
import React, { memo } from 'react';
import { Text, View, StyleSheet, AsyncStorage } from 'react-native';
import { theme } from "../core/theme";
class Dashdata extends React.Component{
constructor(){
super();
this.getDataName = this.getDataName.bind(this);
this.state = {
displayname: '',
result: '' // add result here
};
}
getDataName = () => {
const displayname = '';
console.log('getting value from cachedd');
const loginName = AsyncStorage.getItem('#kidssafety:displayname')
.then((result)=>{
console.log(result);
this.setState({result}) // set result to state
});
}
render(){
const { result } = this.state
return(
<View>
{!!result && (<Text style={styles.header}>Welcome Data {result})</Text>}
</View>
)
}
}
const styles = StyleSheet.create({
header: {
fontSize: 22,
color: theme.colors.primary,
fontWeight: "bold",
paddingVertical: 14,
flex: 1,
marginTop: 100,
width: '100%',
textAlign: 'left'
}
});
export default memo(Dashdata);
I have multiple videos in the swiper to show videos one by one, but all the videos are loaded and playing at the same time and audios are messed up, I want current video only play at a time.
import * as React from 'react';
import { Text, View, StyleSheet,Image, Dimensions } from 'react-native';
import { Constants } from 'expo';
import { Video } from 'expo';
import Swiper from './Swiper';
import InViewPort from './InViewport';
const screenWidth = Dimensions.get('window').width ;
const screenHeight = Dimensions.get('window').height;
export default class App extends React.Component {
constructor(props) {
super(props);
// Your source data
this.state = {
images: {},
muted : false,
paused: true,
};
this.player = Array();
this.onChangeImage = this.onChangeImage.bind(this);
}
videoError(err){
console.warn(err);
}
pauseVideo = () => {
var curr = this.state.currentIndex;
console.warn(curr);
if(this.player[curr]) {
this.setState({paused: true });
}
}
playVideo = () => {
var curr = this.state.currentIndex;
console.warn(curr);
if(this.player[curr]) {
this.setState({paused: false});
}
}
handlePlaying = (isVisible) => {
isVisible ? this.playVideo() : this.pauseVideo();
}
onChangeImage (index) {
this.setState({ currentIndex: index});
}
render() {
let items = Array.apply(null, Array(15)).map((v, i) => {
return {
id: i,
caption: i + 1,
source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
dimension: '{ width: 150, height: 150 }',
};
});
return(
<View style={styles.DefaultView}>
<Swiper
showsPagination={false}
onIndexChanged={this.onChangeImage}
index={0}
>
{items.map((item, key) => {
if(key==1 || key ==5){
return (
<InViewPort onChange={this.handlePlaying} key={key}>
<Video onError={this.videoError}
muted={this.state.muted}
paused={this.state.paused}
source={{uri: 'http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4' }}
style={styles.backgroundVideo}
ref={(ref) => {
this.player[key] = ref;
}}
controls={true}
/>
</InViewPort>
)
}else{
return(
<Image
resizeMode='contain'
style={{width:screenWidth, height: screenHeight}}
source={item.source}
key={key}
/>
)
}
})}
</Swiper>
</View>
)
}
}
const styles = StyleSheet.create({
scrollView: {
flex: 1,
flexDirection: 'row',
},
DefaultView: {
flex: 1,
backgroundColor: '#000',
width: screenWidth,
justifyContent:'center',
alignItems:'center'
},
iconContainer: {
flexDirection: "row",
justifyContent: "space-evenly",
width: 150,
},
backgroundVideo: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
width: screenWidth,
height: 300,
marginTop:'50%',
position:'absolute',
},
});
I need some idea on this, we have a player reference to be used, also swiper component have onIndexChanged which will trigger when we moved to next video, how we can link the reference of the player to onIndexChanged and when we do swipe how we make it current video only to play?
As per Andrew suggestion I have used InPortView component too determine the current view of swipe, but still I am not sure how to make reference for video elements to be used in the functions for play and pause the concern video.
Components used:
For video react-native-video
For Swiper : react-native-swiper
Updated Full code with Expo example : Expo Snack
So taking your snack. I managed to get it to work.
I moved the Video out into its own component and passed a few additional props to it, the index in the array and the currentIndex showing.
export default class App extends React.Component {
constructor(props) {
super(props);
// Your source data
this.state = {
images: {},
muted : false,
paused: true,
currentIndex: 0
};
}
onChangeImage = (index) => {
console.log('currentIndex ', index)
this.setState({ currentIndex: index});
}
render() {
let items = Array.apply(null, Array(15)).map((v, i) => {
return {
id: i,
caption: i + 1,
source: { uri: 'http://placehold.it/200x200?text=' + (i + 1) },
dimension: '{ width: 150, height: 150 }',
};
});
return(
<View style={styles.DefaultView}>
<Swiper
showsPagination={false}
onIndexChanged={this.onChangeImage}
index={0}
>
{items.map((item, key) => {
if(key==1 || key ==5){
return (
<VideoPlayer key={key} index={key} currentIndex={this.state.currentIndex}/>
)
}else{
return(
<Image
resizeMode='contain'
style={{width:screenWidth, height: screenHeight}}
source={item.source}
key={key}
/>
)
}
})}
</Swiper>
</View>
)
}
}
The video component uses react-native-inviewport to help handle whether or not it is in the viewport. However it doesn't play nicely with react-native-swiper but it is possible to get it to work.
export default class VideoPlayer extends React.Component {
pauseVideo = () => {
if(this.video) {
this.video.pauseAsync();
}
}
playVideo = () => {
if(this.video) {
this.video.playAsync();
}
}
handlePlaying = (isVisible) => {
this.props.index === this.props.currentIndex ? this.playVideo() : this.pauseVideo();
}
render() {
return (
<View style={styles.container}>
<InViewPort onChange={this.handlePlaying}>
<Video
ref={ref => {this.video = ref}}
source={{ uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4' }}
rate={1.0}
volume={1.0}
isMuted={false}
resizeMode="cover"
shouldPlay
style={{ width: WIDTH, height: 300 }}
/>
</InViewPort>
</View>
)
}
}
When I used the InViewPort component alone it seemed to think that the video in position 6 was in the viewport and would play it. So what I use the InviewPort is to perform a check to compare the index of the video with the currentIndex if they match play the video otherwise pause. I suppose this could be updated to use componentDidUpdate to handle the changes in the props. However, additional checks will need to be performed when the component mounts so that it doesn't play the video.
Here is my snack with it working. https://snack.expo.io/#andypandy/swiper-video
I have a HOC made in recompose that isn't behaving properly - it just shows the component at it's end value without ever animating. The same component, written as a regular class, behaves fine. Can someone tell me what is causing this issue, or how I would approach this so it works correctly?
recompose HOC component:
const enhancer = compose(
withState('slideAnim', 'setSlide', new Animated.Value(width)),
withState('fadeAnim', 'setFadeAnim', new Animated.Value(0)),
lifecycle({
componentDidMount () {
Animated.parallel([
Animated.timing(this.props.slideAnim, {
toValue: 0,
duration: 500
}),
Animated.timing(this.props.fadeAnim, {
toValue: 1,
duration: 500
})
]).start()
}
})
)
const ModalScene = ({ children, slideAnim, fadeAnim }) => {
return (
<Animated.View style={[styles, { opacity: fadeAnim, left: slideAnim }]}>
{children}
</Animated.View>
)
}
regular class:
class ModalScene extends React.Component {
constructor (props) {
super(props)
this.state = {
slideAnim: new Animated.Value(width),
fadeAnim: new Animated.Value(0)
}
}
componentDidMount () {
Animated.parallel([
Animated.timing(this.state.slideAnim, {
toValue: 0,
duration: 500
}),
Animated.timing(this.state.fadeAnim, {
toValue: 1,
duration: 500
})
]).start()
}
render () {
return (
<Animated.View
style={[
styles,
{ opacity: this.state.fadeAnim, left: this.state.slideAnim }
]}
>
{this.props.children}
</Animated.View>
)
}
}
Your code is correct, you just forget to export HOC, try this:
export default enhancer(ModalScene);
I had a similar problem which seemed to be caused by the mutable nature of the Animated objects. To make sure an instance of Animated.Value was created for each instance of my components I had to use the other form of withState, the one that takes a function as initial value. Try changing these lines:
withState('slideAnim', 'setSlide', new Animated.Value(width)),
withState('fadeAnim', 'setFadeAnim', new Animated.Value(0)),
to
withState('slideAnim', 'setSlide', () => new Animated.Value(width)),
withState('fadeAnim', 'setFadeAnim', () => new Animated.Value(0)),
I wish to use css-transition to animate an object:
<div style={this.setStyle()}>
setStyle () {
const style = {}
if (this.state.fullScreen) {
style.background = 'white'
style.position = 'absolute'
style.transition = 'top 2s'
style.top = '20px'
}
//here I wish to set style.top = 0
return style
}
I wish to first set the style.top = 20px (this is where the item is already and then re render the dom and then set the style.top = 0 to trigger the animation. How can this be done?
state declaration:
constructor (props) {
super(props)
this.state = {
active: -1,
fullScreen: false,
fullScreenStyle: {
background: 'transparent',
position: 'static',
top: 'auto'
}
}
this.flky = {}
}
setStyle () {
if (this.state.fullScreen) {
this.setState({
fullScreenStyle.background: 'white,
fullScreenStyle.position: 'absolute'
fullScreenStyle.top: '20px'
})
}
To rerender the Dom you have two options:
1) by setting the state use setState.
2) by using lifecycle function that is forceUpdate()
But you have to take care before using forceupdate function because it stops other operations and invoke render function, using setState is recommended.
In this you can do one thing:
constructor(props)
{
super(props)
this.state={
style:{
background = 'white',
position = 'absolute',
transition = 'top 2s',
top:'20px'
}
}
}
<div style={this.setStyle()}>
setStyle () {
//you can set state as follows
if (this.state.fullScreen) {
this.setState({
style:{background: 'white'},
style:{position:'absolute'},
style:{transition: 'top 2s'},
style:{top: '20px'}
)}
}
//here I wish to set style.top = 0
else
{
this.setState({ style:{background: 'white'},
style:{position:'absolute'},
style:{transition: 'top 2s'},
style:{top: '0px'}
)}
}
}