1

I created a React Native component that consists of 5 icons in a row. The icons are clickable and I want to animate the one that is clicked.

My problem is: When I click an icon, ALL the icons are animated. This is because they are produced in a loop and all given the same properties.

How can I set up my component so that I can somehow only animate the one icon that is pressed?

Here's the component:

import React from 'react';
import { StyleSheet, Animated, View, Text, TouchableHighlight, } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
const AnimatedIcon = Animated.createAnimatedComponent(Icon);

export class IconRow extends React.Component {  

  constructor(props) {
    super(props);
    this.state = { iconFontSize: new Animated.Value(50) };
  }

  onIconPress = (index) => {
    Animated.sequence([
      Animated.timing(this.state.iconFontSize, { toValue: 40, duration: 100 }),
      Animated.timing(this.state.iconFontSize, { toValue: 58, duration: 100 }),
      Animated.timing(this.state.iconFontSize, { toValue: 50, duration: 100 }),
    ]).start();
  }

  renderIcons() {
    var icons = [];
    for (var i = 0; i < 5; i++) {
      icons.push(
        <TouchableHighlight key={i} underlayColor="transparent" onPress={this.onIconPress.bind(this, i)}>       
          <AnimatedIcon name="heart" style={{fontSize:this.state.iconFontSize, color: "red"}} />
        </TouchableHighlight>   
      );        
    }
    return icons;
  }

  render() {
    return (
        <View style={{flexDirection: "row"}}>
          {this.renderIcons()}
        </View>
    );
  }
}

Snack: https://snack.expo.io/HJJ0Edhlz

Eric
  • 2,098
  • 4
  • 30
  • 44

3 Answers3

3

@Eric - I'm unable to test this locally, but I'm pretty sure it will do what you want. If it doesn't work out, please let me know and I'll remove my answer.

import React from 'react';
import { StyleSheet, Animated, View, Text, TouchableHighlight, } from 'react-native';
import Icon from 'react-native-vector-icons/MaterialCommunityIcons';
const AnimatedIcon = Animated.createAnimatedComponent(Icon);

export class IconRow extends React.Component {  

  constructor(props) {
    super(props);
    this.state = { 
      iconFontSizes: [
        new Animated.Value(50),
        new Animated.Value(50),
        new Animated.Value(50),
        new Animated.Value(50),
        new Animated.Value(50)
      ], 
    };
  }

  onIconPress = (i) => {
    Animated.sequence([
      Animated.timing(this.state.iconFontSizes[i], { toValue: 40, duration: 100 }),
      Animated.timing(this.state.iconFontSizes[i], { toValue: 58, duration: 100 }),
      Animated.timing(this.state.iconFontSizes[i], { toValue: 50, duration: 100 }),
    ]).start();
  }

  renderIcons() {
    var icons = [];

    for (var i = 0; i < this.state.iconFontSizes.length; i++) {
      icons.push(
        <TouchableHighlight key={i} underlayColor="transparent" onPress={this.onIconPress.bind(this, i)}>       
          <AnimatedIcon name="heart" style={{fontSize:this.state.iconFontSizes[i], color: "red"}} />
        </TouchableHighlight>   
      );        
    }

    return icons;
  }

  render() {
    return (
        <View style={{flexDirection: "row"}}>
          {this.renderIcons()}
        </View>
    );
  }
}
Eric
  • 2,098
  • 4
  • 30
  • 44
trevor
  • 2,300
  • 1
  • 14
  • 13
  • This should work but instead of mutating state, you would need to store them in an array then use `setState` to update the state after the loop. – Matt Aft Nov 29 '17 at 19:03
  • @Matt Aft Are the changes I made above what you were proposing? – trevor Nov 29 '17 at 19:52
  • 1
    yeah, you just didn't need to concat. I just edited that part, but everything else looks good – Matt Aft Nov 29 '17 at 20:49
  • @trevor I will test in a moment, but I don't believe you can set the state inside a render method, can you? I get the idea, though. – Eric Nov 29 '17 at 23:58
  • 1
    @Eric I'm not 100% sure that you can. Like I said, I was unable to test this locally. – trevor Nov 30 '17 at 00:02
  • Perfect! I made a small edit regarding the setting state, but this is exactly what I needed! Thank you so much! – Eric Nov 30 '17 at 00:04
  • 1
    Ok, awesome! Do you want to share the edit you made so we can update the answer? – trevor Nov 30 '17 at 00:06
  • I edited your actual post, but it says it needs to be peer reviewed. :/ Does it let you approve it? All I did was initialize iconFontSizes[] in the constructor. – Eric Nov 30 '17 at 00:10
  • 1
    It looks like you need at least 2K reputation to access the suggested edits review queue, so I won't be able to approve it, but someone will. Glad this worked out. Good luck with your project. And thanks to @Matt Aft for collaborating on this. – trevor Nov 30 '17 at 00:12
2

Here you are creating the whole row of icons that's why you face this problem.

Here you have to create one icon at a time rather than creating a row. Like, take a one view for creating a icon and set in one row

<View style = {{flexDirection:'row'}} >
    <IconRow />
    <IconRow />
    <IconRow />
    <IconRow />
    <IconRow />
</View>

After that, renderIcons function in IconRow class, remove for loop and depended variable 'i' like,

icons.push(
        <TouchableHighlight key={1} underlayColor="transparent" onPress={this.onIconPress.bind(this, 1)}>       
          <AnimatedIcon name="heart" style={{fontSize:this.state.iconFontSize, color: "red"}} />
        </TouchableHighlight> 
);

or you can iterate for loop only one time like,

for (var i = 0; i < 1; i++) {
      icons.push(
        <TouchableHighlight key={1} underlayColor="transparent" onPress={this.onIconPress.bind(this, 1)}>       
          <AnimatedIcon name="heart" style={{fontSize:this.state.iconFontSize, color: "red"}} />
        </TouchableHighlight> 
      );
}

So it creates only one icon at a time and in a row. If you want to give different key than please change '1' to another value.

Now animate the one that is clicked.

Hope It will be helpful.

JAINESH DOSHI
  • 250
  • 2
  • 13
  • So basically you mean to make a separate component for the icon with the animation? Yes, I believe this would work, however I prefer the answer above. Thank you! – Eric Nov 30 '17 at 00:05
0

It is because you are calling the same function which has the same reference in each call. Create a new component and loop in that component so each icon will have its own function with a different reference.

this will be the component of Icon

const Icon = ({ i }) => {
    const iconFontSize = new Animated.Value(50)

    const onIconPress = () => {
        Animated.sequence([
          Animated.timing(iconFontSize, { toValue: 40, duration: 100 }),
          Animated.timing(iconFontSize, { toValue: 58, duration: 100 }),
          Animated.timing(iconFontSize, { toValue: 50, duration: 100 }),
     ]).start();}



    return (
        <TouchableHighlight key={i} underlayColor="transparent" onPress {onIconPress}>       
          <AnimatedIcon name="heart" style={{fontSize: iconFontSize, color: "red"}} />
        </TouchableHighlight>   
    )
}

Bellow will be the parent component where you will import the above Icon component and loop on that. Now in each loop you will have a component and animated functions associated only with that component.

const IconRow = ({ }) => {
    renderIcons = () => {
      var icons = [];
      for (var i = 0; i < 5; i++) {
        icons.push(<Icon i={ i } />);        
      }
      return icons;
    }

    return (
        <View style={{flexDirection: "row"}}>
          {renderIcons()}
        </View> 
    )
}