Victory-native: onPress events do not fire on VictoryChart on iOS 12.2 (test case included)

Created on 20 May 2019  路  8Comments  路  Source: FormidableLabs/victory-native

The Problem

onPress events do not fire when tapping bars on a VictoryChart in iOS 12.2, except for the last bar on the graph. (And most precisely, the right side of the last bar not even the whole bar :)

The same code works in iOS 11.4 and on Android, but does not work on recent versions of iOS (tested on iOS 12.2 both in Simulator and real device).

Reproduction

I have created a Snack sandbox with a test case to reproduce the problem.

Please, take into account that if you run the snack on a cloud device it will work fine, as they use iOS 11.4 (as reported by Platform.Version), so run the code in a local iOs simulator or real device with iOs 12.2.

victory-native: 32.0.2
react-native: 0.59.8
react-native-svg: 9.4.0

Additional information

  • Using the same version of react-native-svg directly to draw Rects with onPress events, the events work fine. So the issue seems to be cased by victory-native and not react-native-svg.
  • If only a VictoryBar is used, the events work fine on all the bars. But when the VictoryBar is wrapped in a VictoryChart (as in the test case) the events stop working. So the issue seems related to VictoryChart.
  • Adding containerComponent={<VictoryContainer disableContainerEvents />}to VictoryChart does not solve the issue
  • Wrapping the chart in Svg and using standalone={false} on the VictoryChart does not solve the issue
  • It happens the same issue with all these events (maybe with others too): onPress, onPressIn, onPressOut

Some more info that may or may not be useful:

  • Running the test case with the latest victory-nativeand Expo on iOs 11.4 works fine and events fire up properly.
  • However, running the test case with the latest victory-nativeand Expo on iOs 12.2 does NOT work fine, but the behaviour is different than with the latest version of all the libraries. (Maybe the behaviour with those versions of the libraries is relevant to identify the underlying problem).
    Latest Expo is 32.0.0 (that internally uses react-native: 0.57.1 and react-native-svg: 8.0.10
  • With Expo on iOs 12.2, events are not fired on bars until the last bar is pressed. After that, events fire on all the bars, but when you change the bar you are tapping on, the event is assigned to the previous bar instead of the one actually tapped. You need to tap the bar twice for the event to be assigned to the right bar.
  • In Android, VictoryChart needs to be wrapped in Svg or events do not fire. Once wrapped, the events work in all the bars, but eventually the app crashes during repeated touches. The eventual crashing on Android may or may not be related to the issue on iOS described here.
  • This issue could be related to some other pending issues, as most open issues seem to be touch-related. The closest one is #471, although that one says click events do not work in any iOs version, while this one is more specific. #425 title refers to event properties, but the real issue seems to be onPressIn events not firing and a later comment of a different user even states that events only fire on the rightmost bar. #440 also states that onPress events do not fire, but the case is more complex (with many charts inside a Flatlist) and does not specify if the issue is in Android or iOS or which version. #433 also has issues with onPress...
    So maybe solving this issue would solve more than one :)
bug help wanted verified

Most helpful comment

@SynerG thank you for your patience and for the thorough reproduction. I will label this issue and try to find time and help to address it.

All 8 comments

@SynerG thank you for your patience and for the thorough reproduction. I will label this issue and try to find time and help to address it.

Any update on this?

Sorry for the delay. We've figured some things out. It looks like recent versions of react-native-svg may be swallowing events. When you use a component like VictoryBar within VictoryChart, the default axes that VictoryChart supplies are rendered _after_ and _above_ the rest of the components. As a temporary work around, you can add your own axes to VictoryChart, and make sure they are _before_ the element you need to add events to. Alternatively, you can use VictoryVoronoiContainer or another solution that attaches events to the parent container instead of any of the individual svg elements.

Hello, I am using react-native 0.60 and react-native-svg 9.11.1. I am having the same issue... I am providing and example of my code that when I use may mouse cursor in the simulator is not working... I think i tried @boygirl options of wrapping my chart with a VictoryVoronoiContainer and still no luck getting my events to trigger. Any update on this issue? Or anything in the code below that I am doing wrong?

In the example below, i have also replaced the <VictoryVoronoiContainer> with <VictoryChart> and both ways do not work. I am trying to implement a show and hide a bar according to the legend pressed. For example if legend 1 is pressed for the first time then bar 1 should be hidden, if legend 1 is pressed again then bar 1 should be shown.

import React, {Component} from "react";

import {View, Text} from "react-native";
import {Svg} from "react-native-svg";
import {VictoryChart, VictoryLegend, VictoryBar, VictoryVoronoiContainer} from "victory-native";

class ChartLine extends Component {
  constructor(props) {
    super(props);

    this.state = {
      selection: "No Selection",
      fill: "No Fill"
    };
  }

  render() {
    const {selection, fill} = this.state;
    const data = [
      {x: 2, y: 6, name: "W", symbol: {fill: "tomato", type: "star"}},
      {x: 4, y: 4, name: "R", symbol: {fill: "orange"}},
      {x: 6, y: 2, name: "E", symbol: {fill: "gold"}},
      {x: 8, y: 4, name: "S", symbol: {fill: "tomato"}}
    ];

    return (
      <View>
        <Svg width={400} height={400}>
          <VictoryVoronoiContainer>
            <VictoryLegend x={80} y={10}
                           title={"Legends"}
                           centerTitle
                           orientation={"horizontal"}
                           gutter={30}
                           style={{
                             zIndex: 1000,
                             backgroundColor: "Blue",
                             border: {
                               stroke: "black"
                             },
                             title: {
                               fontSize: 20
                             }
                           }}
                           data={data}
                           events={
                             [{
                               target: "data",
                               eventKey: "all",
                               eventHandlers: {
                                 onPress: (event, legend) => {
                                   return [{
                                     target: "data",
                                     mutation: (props) => {
                                       const barFill = props && props.style && props.style.fill;

                                       this.setState({
                                         selection: legend.index,
                                         fill: barFill
                                       });

                                       if (barFill === "none") {
                                         return {
                                           style: {
                                             fill: "blue"
                                           }
                                         };
                                       } else {
                                         return {
                                           style: {
                                             fill: "none"
                                           }
                                         };
                                       }
                                     }
                                   }];
                                 }
                               }
                             }]
                           }/>
            <VictoryBar data={data}/>
          </VictoryVoronoiContainer>
        </Svg>
        <Text>{selection}</Text>
        <Text>{fill}</Text>
      </View>
    );
  }
}

export default ChartLine;

Any update on this? I have tried these combinations:

import React, {Component} from "react";

import {View, Text} from "react-native";
import {Svg} from "react-native-svg";
import {VictoryChart, VictoryLegend, VictoryBar, VictoryVoronoiContainer, VictoryLine} from "victory-native";

class ChartLine extends Component {
  constructor(props) {
    super(props);

    this.state = {
      selection: "No Selection",
      fill: "No Fill"
    };
  }

  render() {
    const {selection, fill} = this.state;
    const data = [
      [
        {x: 1, y: 6, fill: "tomato"},
        {x: 4, y: 4, fill: "tomato"},
        {x: 6, y: 2, fill: "tomato"},
        {x: 8, y: 4, fill: "tomato"}
      ],
      [
        {x: 1, y: 4, fill: "gold"},
        {x: 3, y: 4, fill: "gold"},
        {x: 5, y: 1, fill: "gold"},
        {x: 4, y: 8, fill: "gold"}
      ]
    ];

    return (
      <View>
        <Svg width={400} height={400}>
          <VictoryChart
            width={400}
            height={400}
            events={[{
              childName: ["legend"],
              target: "data",
              eventKey: "all",
              eventHandlers: {
                onPressIn: (event, legend) => {
                  window.alert(legend);

                  return [{
                    childName: `line-0`,
                    target: "data",
                    eventKey: "all",
                    mutation: (props) => {
                      const lineFill = props && props.style && props.style.fill;

                      this.setState({
                        selection: legend.index,
                        fill: lineFill
                      });

                      if (lineFill === "none") {
                        return {
                          style: {
                            fill: "blue"
                          }
                        };
                      } else {
                        return {
                          style: {
                            fill: "none"
                          }
                        };
                      }
                    }
                  }];
                }
              }
            }]}>
            {
              data.map((line, index) => <VictoryLine key={`line-${index}`} data={line} name={`line-${index}`}/>)
            }
          </VictoryChart>
        </Svg>
        <Text>{selection}</Text>
        <Text>{fill}</Text>
      </View>
    );
  }
}

export default ChartLine;

and

import React, {Component} from "react";

import {View, Text} from "react-native";
import {Svg} from "react-native-svg";
import {VictoryChart, VictoryLegend, VictoryLine} from "victory-native";

class ChartLine extends Component {
  constructor(props) {
    super(props);

    this.state = {
      selection: "No Selection",
      fill: "No Fill"
    };
  }

  render() {
    const {selection, fill} = this.state;
    const data = [
      [
        {x: 1, y: 6, fill: "tomato"},
        {x: 4, y: 4, fill: "tomato"},
        {x: 6, y: 2, fill: "tomato"},
        {x: 8, y: 4, fill: "tomato"}
      ],
      [
        {x: 1, y: 4, fill: "gold"},
        {x: 3, y: 4, fill: "gold"},
        {x: 5, y: 1, fill: "gold"},
        {x: 4, y: 8, fill: "gold"}
      ]
    ];

    return (
      <View>
        <Svg width={400} height={400}>
          <VictoryChart>
            <VictoryLegend x={80} y={10}
                           title={"Lines"}
                           centerTitle
                           orientation={"horizontal"}
                           gutter={30}
                           data={data}
                           name={"legend"}
                           events={
                             [{
                               childName: ["legend"],
                               target: "data",
                               eventKey: "all",
                               eventHandlers: {
                                 onPressIn: (event, legend) => {
                                   window.alert(legend);

                                   return [{
                                     childName: `line-0`,
                                     target: "data",
                                     eventKey: "all",
                                     mutation: (props) => {
                                       const lineFill = props && props.style && props.style.fill;

                                       this.setState({
                                         selection: legend.index,
                                         fill: lineFill
                                       });

                                       if (lineFill === "none") {
                                         return {
                                           style: {
                                             fill: "blue"
                                           }
                                         };
                                       } else {
                                         return {
                                           style: {
                                             fill: "none"
                                           }
                                         };
                                       }
                                     }
                                   }];
                                 }
                               }
                             }]
                           }/>
            {
              data.map((line, index) =><VictoryLine key={`line-${index}`} data={line} name={`line-${index}`}/>)
            }
            {/*<VictoryLine data={data[0]} name={`line-${0}`}/>*/}
            {/*<VictoryLine data={data[1]} name={`line-${1}`}/>*/}
          </VictoryChart>
        </Svg>
        <Text>{selection}</Text>
        <Text>{fill}</Text>
      </View>
    );
  }
}

export default ChartLine;

plus the comment above this one and none work for onPress or onPressIn. Any help?

victory-native: 32.0.2
react-native: 0.59.9
react-n ative-svg: 9.11.1

Hi! just change the event name 'onClick' to 'onPress', it works on ios! but not work on android!

victory-native: 35.0.1
react-native: 0.63.2
react-native-svg: 12.1.0
ios:13.6

Any update onPress works on emulator but no on real device

Why was this closed? Is there a resolution? I still hit this with 12.1.0 and with victory-native 35.3.2

Was this page helpful?
0 / 5 - 0 ratings