OnLayout to get image Dimensions crash on second render - javascript

i need a bit of help. Im trying to implement a screen in React native to place 'Tags' like instagram does in his photos. My workaround to this problem is to get the current image onLayout props and render based on that info. The screen takes the image width and height, and places the tags in the equivalent precentaje location of the image. It works just fine until i re open the Tagging screen a second time and the app just crash...
The reson of the crash it's because im styling the positions of the tags dynamiclly in the styled component (left:'X'px, top: 'X'px), but as i open the Tagging screen a second time, the onLayout of my image returns an undefined and the styled component crashes at trying to set left: 'undefined'px (or top)
i tried everything and i'm out of ideas... Here is the code im using (Sorry, it's a mess...)
import React, {useState} from 'react';
import {
Dimensions,
LayoutRectangle,
NativeTouchEvent,
StyleSheet,
View,
} from 'react-native';
import {Text} from '../../styles';
import LazyImage from '../LazyImage';
import {TagsContainer, TouchWrapper, TagItem} from './styled';
interface iTags {
id: string;
name: string;
locationX: number;
locationY: number;
}
interface iProps {
imageUri: string;
tagsArray: Array<iTags>;
onTouch: (percentX: number, percentY: number) => void;
onDeleteTag: (Tag: iTags) => void;
}
const ImageTaged: React.FC<iProps> = (props) => {
const [isImageLoading, setImageLoading] = useState(true);
const [getLayout, setLayout] = useState<LayoutRectangle>(
{} as LayoutRectangle,
);
function handleLayout(layout: LayoutRectangle) {
setLayout(layout);
}
function handleEvent(e: NativeTouchEvent) {
const {width, height} = getLayout;
const percentageX = Number(((e.locationX * 100) / width).toFixed(2));
const percentageY = Number(((e.locationY * 100) / height).toFixed(2));
props.onTouch(percentageX, percentageY);
}
function deleteTag(tag: iTags) {
props.onDeleteTag(tag);
}
function handleLoadEnd() {
setImageLoading(false);
}
return (
<TagsContainer
onTouchStart={({nativeEvent}) =>
isImageLoading ? undefined : handleEvent(nativeEvent)
}>
<LazyImage
sourceUri={props.imageUri}
onLayout={handleLayout}
onLoadEnd={handleLoadEnd}
/>
{props.tagsArray &&
props.tagsArray.map((c: iTags) => (
<TagItem
key={c.id}
locationX={(c.locationX * getLayout.width - 22) / 100}
locationY={(c.locationY * getLayout.height) / 100}
onTouchStart={() => deleteTag(c)}>
<View style={styles.tagTriangle} />
<View style={styles.tagUserView}>
<Text style={styles.tagListText}>
{c.name.length > 10
? c.name.substring(0, 11) + '. . .'
: c.name}
</Text>
</View>
</TagItem>
))}
</TagsContainer>
);
};
const styles = StyleSheet.create({
/
tagTriangle: {
height: 0,
width: 0,
left: 7,
borderLeftColor: 'transparent',
borderLeftWidth: 7,
borderRightColor: 'transparent',
borderRightWidth: 7,
borderBottomColor: 'rgba(244, 147, 144, 0.8)',
borderBottomWidth: 7,
},
tagUserView: {
backgroundColor: 'rgba(244, 147, 144, 0.8)',
borderRadius: 5,
borderWidth: 1,
borderColor: 'rgba(244, 147, 144, 0.8)',
paddingLeft: 10,
paddingRight: 10,
paddingTop: 3,
paddingBottom: 3,
flexDirection: 'row',
},
tagListText: {
color: 'white',
fontWeight: '800',
},
});
export default ImageTaged;
And just in case the Tag styled component used to render the Tag
export const TagItem = styled.View`
position: absolute;
top: ${(props) => `${props.locationY}px`};
left: ${(props) => `${props.locationX}px`};
justify-content: center;
`;
The component basiclly is receiving an Array of tags it needs to display in a modal when the user touches the Image, then the user chooses an option from the displayed Modal and the tag gets displayed on top of the Image.
I took this exact concept from a npmjs.com package called 'react-native-image-tagging-same-like-instagram'

I don't know how you implement the TagItem but it is clear that it cant accept undefined as locationX or locationY. so I think you can pass 0 for example if the coordinates haven't been calculated. and in the tagItem check if it is 0 then simply hide the tag.
locationX={c && c.locationX && getLayout && getLayout.width ? (c.locationX * getLayout.width - 22) / 100 : 0}
locationY={c && c.locationY && getLayout && getLayout.height ? (c.locationY * getLayout.height) / 100 : 0}

Thanks everybody for your answers. I just fixed the issue because the Tags tryes to display themselves before onLayout method was called, so they returned undefined to the stylesheet, and therefore, the error.
Thanks to some answers and some thinking trough, i realized all i had to do is, like Alireza replied, check if layout Obj != undefined before rendering the tags. So dumb from my part haha.
Thanks, now the code is working

Related

How can we pass the functions inside of single sx properties , say ( color, zIndex, backgroundColor) , etc? Is this possible somehow?

I am making a single search component for all the different places into my application.
And to each component I am passing a prop named search : string to check for the truthfulness of that particular component, so as to specify styles for the same.
I know I could use classNames instead of sx prop, but if I pass the sx prop which contain where every single property received the function , which in turn behaves accordingly to the type of which component is active for that moment. ( or way could say , which component has active), this is because my search bar for homepage has different styling then the search bar for items page.
Here's is my code
import { SearchSharp } from "#mui/icons-material";
import { alpha, FormControl, IconButton, InputAdornment, InputBase, makeStyles, styled, SxProps, TextField, Theme, useTheme } from "#mui/material";
import { Fragment } from "react";
import { useThemeContext } from "../../css/ThemeSettings";
import { useAppDispatch, useAppSelector } from "../../Redux/reduxStore";
import { setSearchTerm } from "./jobSlice";
interface Props {
search? : string;
}
export default function SearchAll ( props : Props ) {
// using redux
const { searchTerm } = useAppSelector ( state => state.jobs);
const dispatch = useAppDispatch();
// using theme
const { darkMode } = useThemeContext();
// background color // this is my function to trigger different backgroundColor based on which component is active...
const setBackgroundColor = () => {
switch ( props.search ) {
case 'home' : return darkMode ? 'rgba(0, 0, 0, 0.54)' : 'rgba(238, 240, 243, 1)';
case 'items' : return 'rgba( 255, 255, 255, 1 )';
}};
// similar to the above, i want to make other functions that I could pass into color, // width, position property etc.
// making and using styles
const searchStyles = {
position : 'relative',
borderRadius : 20,
color : 'text.secondary',
width : props.search === 'home' ? '500px' : '100%',
backgroundColor : setBackgroundColor(),
"& .MuiOutlinedInput-root": {
"& > fieldset": { border : "none" } },
'&:hover': {
backgroundColor : props.search === 'items' ? 'none'
: darkMode ? 'rgba(0, 0, 0, 0.54)' : 'rgba(238, 240, 243, 1)',
},
};
return (
<Fragment>
<TextField
variant = {'outlined'}
size = {'small'}
sx = {searchStyles} // i am getting an error here
placeholder = {"Search…"}
fullWidth = { props.search === 'items' ? true : false }
autoFocus = { true } />
</Fragment>
)
}
below is the error which I am getting when hovering on 'sx' keyword, as it is the thing which is throwing the error ...
The system prop that allows defining system overrides as well as additional CSS styles.
Type '{ position: string; borderRadius: number; color: string; width: string; backgroundColor: string | undefined; "& .MuiOutlinedInput-root": { "& > fieldset": { border: string; }; }; '&:hover': { backgroundColor: string; }; }' is not assignable to type 'SxProps<Theme> | undefined'.
Type '{ position: string; borderRadius: number; color: string; width: string; backgroundColor: string | undefined; "& .MuiOutlinedInput-root": { "& > fieldset": { border: string; }; }; '&:hover': { backgroundColor: string; }; }' is not assignable to type 'CSSSelectorObjectOrCssVariables<Theme>'.
Property 'backgroundColor' is incompatible with index signature.
Type 'string | undefined' is not assignable to type 'SystemStyleObject<Theme> | CssVariableType | ((theme: Theme) => string | number | SystemStyleObject<Theme>)'.
Type 'undefined' is not assignable to type 'SystemStyleObject<Theme> | CssVariableType | ((theme: Theme) => string | number |
Is there any way, we could use to pass functions ( which in turn return string) to these individual sx props?
or is there any another way to achieve this?
your setBackgroundColor you use for setting backgroundColor in your searchStyles not compatible with the property backgroundColor (Property 'backgroundColor' is incompatible with index signature.) as is could possible return undefined while backgroundColor requires a "SystemStyleObject | CssVariableType | ((theme: Theme) => string | number | SystemStyleObject)" and does not accept undefined as a valid type
You should be able to fix this by adding a default case to your setBackGroundColor method
const setBackgroundColor = () => {
switch ( props.search ) {
case 'home': return darkMode ? 'rgba(0, 0, 0, 0.54)' : 'rgba(238, 240, 243, 1)';
case 'items': return 'rgba( 255, 255, 255, 1 )';
default: return 'none'
}};

How to pass a variable to React Native Stylesheet? [duplicate]

This question already has answers here:
Passing props into external stylesheet in React Native?
(7 answers)
Closed 11 months ago.
I want to pass a variable to the "shadowColor" property in my stylesheet from an array in the code above but I am getting a "Can't find name " error. I tried to use a template literal but it's not working. Any help is appreciated!
import { View, Text, StyleSheet, Image } from "react-native";
import React, { useState } from "react";
import { LinearGradient } from "expo-linear-gradient";
export default function Card(props: any) {
const colors = [
["#FE90B0", "#F45283", "#ff0047"],
["#89E5FE", "#43C5E9", "#00c9ff"],
["#AE9CFF", "#927FFC", "#2700ff"],
["#FED3A0", "#FFA741", "#ff8800"],
];
return (
<View
style={[styles.card, styles.shadow, { shadowColor: `${colors[0][2]}` }]}
>
// .... unrelated code
}
const styles = StyleSheet.create({
card: {
height: "33%",
width: "85%",
borderRadius: 35,
},
shadow: {
shadowColor: `${colors[0][2]}`,
shadowOffset: {
width: 0,
height: 18,
},
shadowOpacity: 0.25,
shadowRadius: 20.0,
elevation: 24,
},
fonts: {
padding: 15,
},
img: {
width: "100%",
height: "95%",
borderTopRightRadius: 20,
borderTopLeftRadius: 20,
},
});
The reason this is happening is because you declare you colors variable inside of your Card component, but you try to use your colors variable outside of your Card's scope. There are several solutions, depending on what you want to do:
Lift the colors variable up and make it a module-scoped array:
const colors = [
["#FE90B0", "#F45283", "#ff0047"],
["#89E5FE", "#43C5E9", "#00c9ff"],
["#AE9CFF", "#927FFC", "#2700ff"],
["#FED3A0", "#FFA741", "#ff8800"],
];
export default function Card(props: any) { /* ... snip ... */ }
const styles = StyleSheet.create({ /* ... snip ... */ });
Use the technique described in the question #Odunsi linked to pass the selected values into your StyleSheet.create call:
const stylesFor = colors => StyleSheet.create({ /* ... snip ... */ });
export default function Cards(props: any) {
return <View style={stylesFor(colors).SOME_CLASS} />;
}

Media query ReactJs component

I want to add a media query in react to check the size of the screen before applying width.
This is my code :
const myConfig = {
nodeHighlightBehavior: true,
node: {
color: "lightgreen",
size: 120,
highlightStrokeColor: "blue",
labelProperty: "name"
},
link: {
highlightColor: "lightblue"
},
#media (max-width: 960px){
width: window.innerWidth * 0.9
};
Error: Line 76: Parsing error: Stage 2 decorators disallow object literal property decorators
Media queries are a CSS property and you are using it as a JavaScript attribute.
You either need to write the media query on CSS and apply it to a component, perhaps a global wrapper component.
Or you use JavaScript to get the width of the page and then set it on your myConfig, for this you can use DOM methods like offsetWidth
const documentWidth = document.body.offsetWidth
...
},
width: documentWidth < 960 ? window.innerWidth * 0.9 : window.innerWidth * 0.6
You could create a higher order component (HOC) to solve this issue. Using something like the react-media library (you would need to install the react-media library) one could have the following:
import React from 'react';
import Media from 'react-media';
class OriginalComponent extends React.Component {
render() {
const myConfig = {
nodeHighlightBehavior: true,
node: {
color: "lightgreen",
size: 120,
highlightStrokeColor: "blue",
labelProperty: "name"
},
link: {
highlightColor: "lightblue"
}
}
if (this.props.small) {
myConfig.width = window.innerWidth * 0.9
}
return (
<div style={myConfig} />
)
}
}
class App extends React.Component {
render() {
return (
<div>
<Media query="(max-width: 960px)">
{matches =>
<OriginalComponent small={matches} />
}
</Media>
</div>
);
}
}
But having a HOC might be a little overkill for your usage.
You must import that from file.css because CSS in JS don't support it, then use className.
Else, you can use react-responsive

How to create button with text under icon by reactjs?

Now, I have component like this:
code of it:
import React from "react";
import {withStyles} from "material-ui/styles";
import Settings from "material-ui-icons/Settings";
import Button from "material-ui/Button";
const styles = {
button: {
color: "primary",
height: 95,
width: 95,
disableRipple: "true",
focusRipple: "true",
},
icon: {
height: 35,
width: 35,
display: "block",
float: "none",
},
text: {
height: 35,
width: 35,
display: "block",
float: "none",
marginTop: 10,
},
};
/* eslint-disable react/prop-types */
const IconedLabel = ({classes}) => (
<section>
<Button className={classes.iconButton} variant="raised" color="primary">
<Settings className={classes.icon}/>
<div className={classes.text}>Message</div>
</Button>
</section>
);
export default withStyles(styles)(IconedLabel);
But need to button, that in top part contains icon and text message in bottom.
I use reactjs and material-ui lib from here https://material-ui-next.com/demos/buttons/
The Button component uses flexbox to control the layout/alignment of content. To align the content vertically (so the icon is above the text), you can simply change the flex-direction to column.
This style needs to be applied to an element inside the button component, not to the root element. You can use the classes property to override all of the styles in a component.
In this case, you want to add flexDirection: column to the label class.
Documentation on class overrides in material ui v1
Here's a working example. Hope it helps.
const [React, ReactDOM, Button, Settings, withStyles] = [window.React, window.ReactDOM, window['material-ui'].Button, ({className}) => <i className={`material-icons ${className}`}>settings</i>, window['material-ui'].withStyles]
// Ignore code above this line
const styles = theme => ({
button: {
height: 95, // setting height/width is optional
},
label: {
// Aligns the content of the button vertically.
flexDirection: 'column'
},
icon: {
fontSize: '32px !important',
marginBottom: theme.spacing.unit
}
})
const CustomButton = ({ classes }) => (
<Button
/* Use classes property to inject custom styles */
classes={{ root: classes.button, label: classes.label }}
variant="raised"
color="primary"
disableRipple={true}
>
<Settings className={classes.icon} />
Message
</Button>
)
const WrappedCustomButton = withStyles(styles)(CustomButton)
ReactDOM.render(<WrappedCustomButton />, document.querySelector('#root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script><script src="https://unpkg.com/material-ui#1.0.0-beta.40/umd/material-ui.production.min.js"></script><link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"><div id="root" />
A (potentially bad) solution would simply be:
.MuiIconButton-label {
flex-direction: column
}
I say bad, because you might want to use it in it's standard format elsewhere.
What I opted to do was add a class name nav-bar-icon-wrapper to the IconButton & set the flex direction in it's parent:
.nav-bar-icon-wrapper {
flex-direction: column
}
.MuiIconButton-label {
flex-direction: inherit
}
If I run into instance later where I want the icon/label button to be standard, I'll just add a new class default-icon-wrapper and css that handles that:
.default-icon-wrapper {
flex-direction: row
}
FWIW:
I preach the BEM http://getbem.com/introduction/ convention AND that whenever you make a component, you add an optional modifier prop.
I have functions in a shared dir that looks these:
export function BEMifyThis(modifier) {
return (klass) => BEMify(klass, modifier)
}
export function BEMify(klass, modifier=false) {
if (modifier) {
klass += ` ${klass}-${modifier}`
}
return klass
}
Then I use that everywhere in my component so the user can access the component elements as a group or individually using their modifiers.
import {BEMifyThis} from '../shared/bem'
const BEMify = BEMifyThis(this.props.modifier)
className={"navbar__menu_item")}
becomes
className={BEMify("navbar__menu_item")}
so something like navbar__menu_item becomes navbar__menu_item navbar__menu_item-logout

Animate Gradient Color React Native

I'm trying to create a gradient in react native that will start as one color when the app opens, and then gradually change into another color every 30 seconds. The regular linear gradient worked without trying to add animation. I tried using interpolation and the Animated timing as seen in the react native documentation, but nothing seems to work.
My Code:
import React, {Component} from 'react';
import {processColor, AppRegistry, StyleSheet, Dimensions, Animated, Image, Easing, View} from 'react-native';
import TimerMixin from 'react-timer-mixin';
import LinearGradient from 'react-native-linear-gradient';
var screenWidth = Dimensions.get('window').width;
var screenHeight = Dimensions.get('window').height;
//HEX version of colors
var gradientColors = [['#EF2A2A', '#EF6A2A'], //Red
['#EF6A2A', '#EFD82A'], //Orange
['#1BD170', '#61E822'], //Green
['#22D2E6', '#26F084'], //Aqua
['#2A3BEF', '#2ADCEF'], //Blue
['#EF2AD2', '#2A3BEF'], //Purple
['#EF2AD2', '#EF2A2A'] //Pink
]
var gradientColorsNoHash = [['EF2A2A', 'EF6A2A'], //Red
['EF6A2A', 'EFD82A'], //Orange
['1BD170', '61E822'], //Green
['22D2E6', '26F084'], //Aqua
['2A3BEF', '2ADCEF'], //Blue
['EF2AD2', '2A3BEF'], //Purple
['EF2AD2', 'EF2A2A'] //Pink
]
/*var gradientColors = [['ef2a2a', 'ef6a2a'], //Red
['ef6a2a', 'efd82a'], //Orange
['1bd170', '61e822'], //Green
['22d2e6', '26f084'], //Aqua
['2a3bef', '2adcef'], //Blue
['ef2ad2', '2a3bef'], //Purple
['ef2ad2', 'ef2a2a'] //Pink
]*/
//RGBA Version of Colors
/*var gradientColors = [['rgba(239, 42, 42, 1)', 'rgba(239, 106, 42, 1)'], //Red
['rgba(239, 106, 42, 1)', 'rgba(239, 216, 42, 1)'], //Orange
['rgba(0, 221, 103, 1)', 'rgba(97, 232, 35, 1)'], //Green
['rgba(34, 210, 230, 1)', 'rgba(38, 240, 132, 1)'], //Aqua
['rgba(42, 59, 239, 1)', 'rgba(42, 220, 239, 1)'], //Blue
['rgba(239, 42, 210, 1)', 'rgba(42, 59, 239, 1)'], //Purple
['rgba(239, 42, 210, 1)', 'rgba(239, 42, 42, 1)'] //Pink
]*/
function hex(c) {
var s = "0123456789abcdef";
var i = parseInt(c);
if (i == 0 || isNaN(c))
return "00";
i = Math.round(Math.min (Math.max (0, i), 255));
//console.log('hex(c) complete!');
return s.charAt((i - i % 16) / 16) + s.charAt(i % 16);
}
// Convert an RGB triplet to a hex string
function convertToHex (rgb) {
return hex(rgb[0]) + hex(rgb[1]) + hex(rgb[2]);
}
// Convert a hex string to an RGB triplet
function convertToRGB(hex) {
var color = [];
color[0] = parseInt(hex.substring(0, 2), 16);
color[1] = parseInt(hex.substring(2, 4), 16);
color[2] = parseInt(hex.substring(4, 6), 16);
return color;
}
function generateColor(colorStart,colorEnd,colorCount) {
// The beginning of your gradient
var start = convertToRGB(colorStart);
// The end of your gradient
var end = convertToRGB(colorEnd);
// The number of colors to compute
var len = colorCount;
//Alpha blending amount
var alpha = 0.0;
var saida = [];
for (i = 0; i < len; i++) {
var c = [];
alpha += (1.0/len);
c[0] = start[0] * alpha + (1 - alpha) * end[0];
c[1] = start[1] * alpha + (1 - alpha) * end[1];
c[2] = start[2] * alpha + (1 - alpha) * end[2];
saida.push(convertToHex(c));
}
return saida;
}
var number = randomIntFromInterval(0,6)
function randomIntFromInterval(min,max) { return Math.floor(Math.random()*(max-min+1)+min); }
const GradientView = React.createClass({
mixins: [TimerMixin],
getInitialState() {
return {
gradIndex: number,
colorTop: gradientColors[number][0],
colorBottom: gradientColors[number][1],
}
},
componentDidMount() {
this.setInterval(() => {
var count = 0
var topGradArray = generateColor(gradientColorsNoHash[this.state.gradIndex][0],(this.state.gradIndex === 6 ? 0 : gradientColorsNoHash[this.state.gradIndex+1][0] ),770);
var bottomGradArray = generateColor(gradientColorsNoHash[this.state.gradIndex][1],(this.state.gradIndex === 6 ? 0 : gradientColorsNoHash[this.state.gradIndex+1][1] ),770);
console.log('Gradients Made');
var clearId = this.setInterval(() => {
if (count == 0) {
this.setState({ clearId: clearId, gradIndex: ( this.state.gradIndex === 6 ? 0 : this.state.gradIndex+1 ) });
console.log('clearId SET!');
}
this.setState({
colorTop: processColor(topGradArray[count]),
colorBottom: processColor(bottomGradArray[count]),
});
count = count+1
if (count == 769) {
console.log('colorTop and Bottom Saved');
this.clearInterval(this.state.clearId)
}
}, 13);
}, 30000);
},
render(){
return(
<LinearGradient colors={[this.state.colorTop, this.state.colorBottom]}>
<View style={styles.translucentContainer}/>
</LinearGradient>
);
}
});
const styles = StyleSheet.create({
translucentContainer: {
width: screenWidth,
height: screenHeight,
backgroundColor: 'white',
opacity: 0.3,
},
});
export default GradientView;
AppRegistry.registerComponent('GradientView', () => GradientView);
UPDATE: After going through many different resources, I've come to the conclusion that the only way to animate the LinearGradient class is to change the color incrementally and rapidly like in their documentation. However, their example is continuous and doesn't allow you to set a desired final color. For my application, I want the gradient to stay one color for 30 seconds, and then go through a 10 second transition to the next color gradient, and then repeat. So for example, it would look like: Red Gradient (30 seconds), Transition Red to Orange (10 seconds), Orange Gradient (30 seconds), Transition Orange to Green (10 seconds), etc.
I'm getting two types of error with this code that seem to alternate. Generally, the first error is this one that appears when the first timer (the 30 second one) goes off:
After dismissing that error message to see what would happen, this error pops up when the same timer goes off again:
At this point I think the source of error is in generating the colors properly in the function contained in componentDidMount()
I found a working solution!
Use linear interpolation to generate your gradient.
This is the simplest way I found to control the gradient properly.
chroma.js :
I found a library called chroma.js that can do this well ! They have a method called scale.colors that can do the job for you!
Install the package :
npm install chroma-js
You can adjust the INTERVAL and the GRADIENT_COLOR_LENGTH constants to change the effect.
Then use the generated spectrum variables in the code :
import React from 'react'
import { AppRegistry, StyleSheet, Dimensions, View } from 'react-native'
import TimerMixin from 'react-timer-mixin'
import LinearGradient from 'react-native-linear-gradient'
import Chroma from 'chroma-js'
var screenWidth = Dimensions.get('window').width
var screenHeight = Dimensions.get('window').height
const TOP_COLORS = ['#EF2A2A', '#EF6A2A', '#1BD170', '#22D2E6', '#2A3BEF', '#EF2AD2', '#EF2AD2']
const BOTTOM_COLORS = ['#EF6A2A', '#EFD82A', '#61E822', '#26F084', '#2ADCEF', '#2A3BEF', '#EF2A2A']
const GRADIENT_COLOR_LENGTH = 700
const TOP_COLORS_SPECTRUM = Chroma.scale(TOP_COLORS).colors(GRADIENT_COLOR_LENGTH)
const BOTTOM_COLORS_SPECTRUM = Chroma.scale(BOTTOM_COLORS).colors(GRADIENT_COLOR_LENGTH)
const INTERVAL = 50
const GradientView = React.createClass({
mixins: [TimerMixin],
getInitialState () {
return {
topIndex: 0,
bottomIndex: 0,
colorTop: TOP_COLORS_SPECTRUM[0],
colorBottom: BOTTOM_COLORS_SPECTRUM[0]
}
},
componentDidMount () {
this.setInterval(() => {
let { topIndex, bottomIndex } = this.state
topIndex++
if (topIndex === TOP_COLORS_SPECTRUM.length) {
topIndex = 0
}
bottomIndex++
if (bottomIndex === BOTTOM_COLORS_SPECTRUM.length) {
bottomIndex = 0
}
this.setState({
topIndex: topIndex,
bottomIndex: bottomIndex,
colorTop: TOP_COLORS_SPECTRUM[topIndex],
colorBottom: BOTTOM_COLORS_SPECTRUM[bottomIndex]
})
}, INTERVAL)
},
render () {
return (
<LinearGradient colors={[this.state.colorTop, this.state.colorBottom]}>
<View style={styles.translucentContainer} />
</LinearGradient>
)
}
})
const styles = StyleSheet.create({
translucentContainer: {
width: screenWidth,
height: screenHeight,
backgroundColor: 'white',
opacity: 0.3
}
})
export default GradientView
AppRegistry.registerComponent('GradientView', () => GradientView)
First, make sure that you followed the steps properly to add LinearGradient to your project : https://github.com/react-native-community/react-native-linear-gradient#add-it-to-your-project
(Try rendering the component with no animation)
Second, fix your code.
You need to call setState each time to trigger a re-rendering.
The <Animated.LinearGradient ...> element you have written is invalid. It doesn't exist. Change it to a LinearGradient element.
In componentDidMount, call setInterval and change the state of the animation inside the callback.
Here is a working example from the documentation:
import React from 'react';
import {
StyleSheet,
Text,
View,
} from 'react-native';
import TimerMixin from 'react-timer-mixin';
import LinearGradient from 'react-native-linear-gradient';
function incrementColor(color, step) {
const intColor = parseInt(color.substr(1), 16);
const newIntColor = (intColor + step).toString(16);
return `#${'0'.repeat(6 - newIntColor.length)}${newIntColor}`;
};
const AnimatedGradient = React.createClass({
mixins: [TimerMixin],
getInitialState() {
return {
count: 0,
colorTop: '#000000',
colorBottom: '#cccccc',
}
},
componentDidMount() {
this.setInterval(() => {
this.setState({
count: this.state.count + 1,
colorTop: incrementColor(this.state.colorTop, 1),
colorBottom: incrementColor(this.state.colorBottom, -1),
});
}, 20);
},
render() {
return (
<View style={styles.container}>
<LinearGradient
colors={[this.state.colorTop, this.state.colorBottom]}
style={styles.gradient} />
<Text style={{color: this.state.colorTop}}>{this.state.colorTop}</Text>
<Text style={{color: this.state.colorBottom}}>{this.state.colorBottom}</Text>
</View>
);
}
});
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
gradient: {
width: 200,
height: 200,
},
});
export default AnimatedGradient;

Categories

Resources