I have a lot of "ready to go" SVGs that I convert with svgr to components.
Inside the SVG there are many paths, each one has a fixed Id that cannot be changed (it is used in other apps).
Q: I need to know which shape the user has touched ... looks simple
In my PoC the only way I found is to modify the component by svgr by adding these attributes for each path (which are hundreds):
ref={this.circleRef} onPress={e => this.onCirclePress(e, this.circleRef.current.props.id)}
I know arrow functions are bad in render, but I need the onPress location coordinates as well.
The onPress event gives me target (runtime id) which is not available from the DOM and therefore I can't associate it to my fixed Id.
Is there really no solution to get the source element pressed by the user? Dropping RN for this would be a pity.
Thank you in advance
Seem you should be able to access this.circleRef.current inside this.onCirclePress even without the arrow function (assuming you have a handler per element), and as far as I see it, that would likely be considered best practise to get access to the props of the element you want. Not sure what part would be missing? Or perhaps I haven't understood correctly.
Alternatively, you can make components that wrap the elements, and customise the handlers any way you want, e.g. like this:
class CircleIdPress extends React.Component {
onPress = e => {
const { onPress, id } = this.props;
if (onPress) {
onPress(e, id);
}
};
render() {
const { onPress, ...rest } = this.props;
return <Circle {...rest} onPress={this.onPress} />;
}
}
Thank you @msand.
I am trying to avoid adding one handler per element as there are hundreds of paths in my drawings.
The arrow function in that example is needed to have a single onPress handler and get both:
locationX, locationY)IdWith regards to the second option, I have two problems:
A third option I was trying is to programmatically add the Refs to all the children of the Svg element. But refs are readonly and I did not find any way to do it. Is that doable?
Just to be sure, are you confirming me that there is no way in React Native to get the source element which originated the event? It looks a huge problem for a lot of applications (I have confirm of this reading other threads on StackOverflow).
Thanks!
I think this shouldn't depend on react-native, only the code inside react-native-svg, no need to feel upset against react-native 馃槈 I think you should be able to override touchableHandlePress(e: GestureResponderEvent)
To something like
touchableHandlePress(e: GestureResponderEvent) {
const { onPress } = this.props;
onPress && onPress(e, this);
}
Really no worries, I am creating a PoC to understand if react native fits well or not :)
Just to be sure, are you suggesting me to patch my local library or is there a better way to override the event argument?
Also, this solution still requires to assign onPress on each child path, right? Or there is a better way to propagate the onPress from Svg to its children?
Thank you!
Well, at least for poc purposes I'd suggest to monkey-patch react-native-svg inside your node_modules, and run npx patch-package react-native-svg. Just to make sure it works to begin with. Yea, requires setting the event handler on each element you wish to have it on.
One option might be to copy the logic from e.g. SvgWithCss
https://github.com/react-native-community/react-native-svg/blob/e66e87a5b5c090509d5e2127237963f83e60f1e9/src/css.tsx#L730-L736
But, using your own middleware function instead of inlineStyles, to transform the xml/svg content to props for react-native-svg components. This would do the transform at runtime, so not efficient, but possibly proof of concept worthy.
Ideally, I'd recommend you make your own transform on top of svgr, and have the event handling logic added for you in some automated way at build time as source code / a code mod, allowing it to run at tailor-made / manually optimized performance levels.
Got it ... will you patch the main package as well? :)
If yes, how long do you believe it will take before going on npm?
With regards to parsing, initially I loaded the big SVGs with xml and it worked, but it was definitely slow. This is why I decided to use svgr. So I hope to be able to easily add it, otherwise I can write a very quick patcher in C# and Linq to XML.
Thank you
Well, the type definition of onPress doesn't have it by default: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/46cb9e443891ccfb931ad823f9b6175aaa14e201/types/react-native/index.d.ts#L888-L892
so I'm not sure it makes sense to have in the main repo, would be kind of surprising for people used to the Text, Touchable, and, Button apis of react-native. And it's also possible to do by defining ones own non-standard svg primitives, essentially extending the exported components of react-native-svg, and importing from an index.js file which exports those instead of from react-native-svg, in any files where you need the special behaviour.
Alternatively, you can just keep using patch-package, if it's only that one type/parameter/argument and nothing else you want to change.
Hmm, I don't think the PoC could be accepted by the customer if there is no 'clean' way to resolve this problem.
Also there is a maintenance problem for every single future release of the react-native-svg.
I am also testing the NativeScript PoC where the SVG support is pretty poor but I already could hit the target with no additions.
Between the two, I am pretty convinced the customer will pick NS to make it easier to maintain.
Let me look at the big picture ...
The SVG component is great to solve three main problem categories: pure drawings (logos, adornments, etc.), graphs w or w/o animations, maps. I am in the third case. where I start from existing complex drawings that needs to be instrumented to give an active functionality.
I am just trying to understand how to use RN to address this category of usage. I mean that if this is not interesting for RN, it is better for me to totally avoid it (no offense intended).
What do you think I should do?
Well, at least it seems very unlikely that this part of react-native-svg would have much churn, I'd be surprised if that patch-package patch would need to be updated for a long time, until the new rearchitecture and event handling system matures and gets released, at which point the patch might become unnecessary. As react-native-svg will probably align with that, and thus become closer to the web/dom as well. If patch-package isn't clean enough for some reason, I'd recommend to do the approach of extending the react-native-svg elements, such that you override touchableHandlePress after calling super in the constructor. Then any dependencies you have which use react-native-svg get the normal api, and any files where you import from your extended components get the convenience of getting a reference to the instance in the onPress handler, without creating a closure for it (which I doubt would be the bottleneck if profiling, even in your use case).
So something like this: https://snack.expo.io/@msand/trusting-candies
This way, you just add './custom-', so instead of
import { Svg, Circle } from 'react-native-svg'
you use
import { Svg, Circle } from './custom-react-native-svg';
App.js
import * as React from 'react';
import { Text, View, StyleSheet } from 'react-native';
import Constants from 'expo-constants';
import { Svg, Circle } from './custom-react-native-svg';
export default function App() {
return (
<View style={styles.container}>
<Svg width="100%" height="100%" viewBox="0 0 100 100">
<Circle
cx="50"
cy="50"
r="50"
id="testid"
fill="black"
onPress={(e, instance) => {
console.log(e, instance);
}}
/>
</Svg>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
paddingTop: Constants.statusBarHeight,
backgroundColor: '#ecf0f1',
padding: 8,
},
});
custom-react-native-svg.js
import * as RNSVG from 'react-native-svg';
const primitives = [
'Svg',
'Circle',
'Ellipse',
'G',
'Text',
'TSpan',
'TextPath',
'Path',
'Polygon',
'Polyline',
'Line',
'Rect',
'Use',
'Image',
'Symbol',
'Defs',
'LinearGradient',
'RadialGradient',
'Stop',
'ClipPath',
'Pattern',
'Mask',
'Marker',
'ForeignObject',
];
const extended = {};
for (let primitive of primitives) {
extended[primitive] = class extends RNSVG[primitive] {
touchableHandlePress = e => {
const { onPress } = this.props;
onPress && onPress(e, this);
};
};
}
const {
Svg,
Circle,
Ellipse,
G,
Text,
TSpan,
TextPath,
Path,
Polygon,
Polyline,
Line,
Rect,
Use,
Image,
Symbol,
Defs,
LinearGradient,
RadialGradient,
Stop,
ClipPath,
Pattern,
Mask,
Marker,
ForeignObject,
} = extended;
export {
Svg,
Circle,
Ellipse,
G,
Text,
TSpan,
TextPath,
Path,
Polygon,
Polyline,
Line,
Rect,
Use,
Image,
Symbol,
Defs,
LinearGradient,
RadialGradient,
Stop,
ClipPath,
Pattern,
Mask,
Marker,
ForeignObject,
};
Thank you.
I am trying the wrapper solution, but I am haveing not such a good time in writing it using typescript.
I guess I will spend a lot of time on that :/
Might be easier to write them out, rather than use a loop to define them, the types should remain the same, except onPress which gets the additional parameter.
Ok, I got the first wrapper working ... easier than expected.
Do you have any idea why the VSCode complains about the id property squiggling it in red?

I've seen that SVGR does not provide a way to customize the generated tags, so I will have to write my own transformation.
Going to a step back, out of my curiosity, the event normally provides the target property which is not useful as it cannot be correlated with the true id. Would not make sense to put a 'reliable' identifier in the event argument object?
The target corresponds to the native View target, and think the reason not to include the instance as an argument or use call to set this to the current instance, has been to encourage one way data-flow and avoid/discourage use of 'this'. While keeping the possibility to override this specific handler, if an escape hatch is actually useful. Ideally you wouldn't look up the id from the instance, but rather read it directly from props or some other way to correlate it while respecting one-way flow. It's usually easier to read/comprehend something more explicit than something implicit / global. So global id's ( and css class names etc.) are discouraged in the react-native world as well. Usually, there are less fragile ways to achieve the desired use-cases.
I get the resonings but it does not work well with hundreds of objects that are not subject to model binding. I am just looking for something giving me the ability to correlate the element with the touch event and that is harder than in any other framework.
In the meanwhile I've gone on with the typescript wrappers and I generated them all.
The red squiggled 'id' in VSCode is definitely a problem because my wrapper does not derive from Shape and the props are wrong.
Also, I can't derive it from Shape because it is not available in the typescript definitions and therefore this solution is not as neat as it should be.
I can see the Id but typescript is screwing up and this won't pass the automated tests.
Do you have any idea on overcoming this problem?
Thank you!
Seems strange, the type definitions should have id as part of the props. I haven't worked with any react-native project using typescript yet, so can't really say. Maybe there's some issue with the type definition in the repo here. Pull requests are welcome!
What does your components look like? Something like this?
import * as RNSVG from 'react-native-svg';
import { GestureResponderEvent } from 'react-native';
class Svg extends RNSVG.Svg {
touchableHandlePress = (e: GestureResponderEvent) => {
const { onPress } = this.props;
// @ts-ignore
onPress && onPress(e, this);
};
}
class Circle extends RNSVG.Circle {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Ellipse extends RNSVG.Ellipse {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class G extends RNSVG.G<{}> {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Text extends RNSVG.Text {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class TSpan extends RNSVG.TSpan {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class TextPath extends RNSVG.TextPath {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Path extends RNSVG.Path {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Polygon extends RNSVG.Polygon {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Polyline extends RNSVG.Polyline {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Line extends RNSVG.Line {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Rect extends RNSVG.Rect {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Use extends RNSVG.Use {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Image extends RNSVG.Image {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Symbol extends RNSVG.Symbol {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Defs extends RNSVG.Defs {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class LinearGradient extends RNSVG.LinearGradient {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class RadialGradient extends RNSVG.RadialGradient {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Stop extends RNSVG.Stop {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class ClipPath extends RNSVG.ClipPath {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Pattern extends RNSVG.Pattern {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Mask extends RNSVG.Mask {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class Marker extends RNSVG.Marker {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
class ForeignObject extends RNSVG.ForeignObject {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
export {
Svg,
Circle,
Ellipse,
G,
Text,
TSpan,
TextPath,
Path,
Polygon,
Polyline,
Line,
Rect,
Use,
Image,
Symbol,
Defs,
LinearGradient,
RadialGradient,
Stop,
ClipPath,
Pattern,
Mask,
Marker,
ForeignObject,
};
Btw, you might want to look into forking https://github.com/kristerkari/react-native-svg-transformer to add extra transforms on top at build time.
Could you share examples of code in other languages / frameworks that work in android, ios both native and/or web in a simpler way than in react-native(-svg)? I haven't found any and would be very interested in seeing that if the option exists?
I generated the components like this:
import React, { Component } from 'react';
import Svg, { Circle } from 'react-native-svg'
export default class SvgwCircle extends React.Component {
onPress = (e: any) => {
const { onPress, id } = this.props as { onPress: any, id: any };
if (onPress) {
onPress(e, id);
}
};
render() {
const { onPress, ...rest } = this.props as { onPress: any, id: any };
return <Circle {...rest} onPress={this.onPress} />;
}
}
But also as follows for those that are not onPress compatible (I generated them just to have space for future enhancements):
import React, { Component } from 'react';
import Svg, { ClipPath } from 'react-native-svg'
export default class SvgwClipPath extends React.Component {
render() {
return <ClipPath {...this.props} />;
}
}
Something like this might help with the types for the id prop:
import * as React from 'react';
import { Text, View, StyleSheet, GestureResponderEvent } from 'react-native';
import * as RNSVG from 'react-native-svg';
class Circle extends React.PureComponent<
{
id: string;
onPress: (e: GestureResponderEvent, id: string) => void;
} & RNSVG.Circle
> {
onPress = (e: GestureResponderEvent) => {
const { onPress, id } = this.props;
if (onPress) {
onPress(e, id);
}
};
render() {
const { onPress, props } = this;
const { onPress: _onPress, ...rest } = props;
// @ts-ignore
return <RNSVG.Circle onPress={onPress} {...rest} />;
}
}
Almost there ... it doesn't recognize Circle in the class declaration:

Thank you very much
Interesting, seems like an issue with the typescript setup. Did you try using this?
https://github.com/react-native-community/react-native-template-typescript#react-native0620-or-higher
I used the default template (--template react-native-template-typescript) coming with version 0.62.0.
It is the same, right?
Can this be the case?
BTW it works, but the change is on index.d.ts

Of course this requires the change to be made to all the definitions in the file.
Seems very possible, the migration to typescript is only like 90% done, I haven't tried using it in any typescript project, and relatively few have contributed improvements / pull requests. Would you mind making one?
@msand Here we go: https://github.com/react-native-community/react-native-svg/pull/1333
Anyway, I am not sure if this is enough. As you can see the squiggles are now in the component using the wrappers. The Svg, Rect and Circle are the wrapped ones:

The current wrappers I generated are here.
This is getting nasty :)
CircleProps and not Circle ... makes sense to meonPress overlaps because the base onPress has a different signature than the wrapper one. It has to be 'overwritten' and I found a way to do it.This change resolves the squiggle issue:
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U; // found on the web
class Circle extends React.PureComponent<
{
id: string;
} & Overwrite<RNSVG.CircleProps, { onPress: (e: GestureResponderEvent, id: string) => void }>
> {
// ...
Does this makes sense to you?
If yes, the pull request is not that important (but can still be useful for other usages and IMO is better to leave there).
Yeah, that looks like it might be correct in terms of typescript types, my knowledge of it is slightly rusty at this point, was thinking pick and keyof would be possible to combine in some way, but haven't read the typescript spec/docs recently, can't say I have a strong grip of all the nuances. Will probably set up a typescript test project soon, and can at that point look at edge-cases of react-native-svg as well.
There are two separate things there:
BTW I am doing more changes to the wrappers to make id and onPress optionals. Will post the link here later on.
I updated the gist to make id and onPress optionals (nullable).
Nullable is needed to avoid the editor complaining on the JSX code when those tags are missing.
The gist contains all the wrappers for the SVG elements which accept all the standard props, the id (which was exposed only on certain types in react-native-svg) and the updated signature of onPress that pass the id in addition of the standard gesture event.
The nullable change is the following:
type Overwrite<T, U> = Pick<T, Exclude<keyof T, keyof U>> & U;
class Circle extends React.PureComponent<
{
id?: string;
} & Overwrite<RNSVG.CircleProps, { onPress?: (e: GestureResponderEvent, id?: string) => void }>
> {
Out of my curiosity, @msand are you willing to accept the pull request or not?
Those parts are only typescript, and have no observable effect on the javascript runtime execution, only the type checker knows about them, if the type-checker doesn't complain and it provides the api you seek, then your job is done.
This kind of api change is essentially the same as creating components for the web, where you use some primitive like e.g. a div, and add/change/hide something in the api, to make it fit the specific use case / business logic and make it become a reusable functional unit in the system. So I think it fits better in project specific code, but e.g. examples and documenting how to do it can certainly be useful.
With regards to what to extend, I'd recommend to try using normal Component rather than PureComponent, it might make sense to use PureComponent somewhere higher up, try to get as much static or rarely changing content inside a single PureComponent, rather than repeating/placing that logic on lots of things that change together.
Aim to group things that depend on common props / change in the same state transitions together, i.e. make components/layers group by frequency of change and set of props they depend on, and do actual measurements with production builds to get accurate observations to based decisions on what bottlenecks to spend time on, and if the change made it better or worse or no change at all.
Alternatively, try extending the RNSVG components directly, and override touchableHandlePress
import { GestureResponderEvent } from 'react-native';
import * as RNSVG from 'react-native-svg';
class Circle extends RNSVG.Circle {
touchableHandlePress = (e: GestureResponderEvent) => {
// @ts-ignore
const { onPress } = this.props;
onPress && onPress(e, this);
};
}
Extending React.Component and composing the RNSVG primitives with the overridden onPress would probably be considered cleaner/purer/best practise, rather than extending the components directly. But in this case I suspect it might give some barely noticeably improvement in memory/cpu use, and probably completely fine for the use case.
Those parts are only typescript, and have no observable effect on the javascript runtime execution
of course, and I believe the pull request is useful to improve the typescript usability
With regards to what to extend, I'd recommend to try using normal Component
ok, makes sense. I just followed your initial suggestion.
The gist is now updated
Alternatively, try extending the RNSVG components directly, and override touchableHandlePress
I am afraid that, from a Typescript perspective it would be more difficult to fix the onPress signature mismatch.
Looks good imo, explicit, clear, slightly repetitive perhaps, but quite much required for typescript, probably not a significant contribution to total app size, and easy to add further tailor-made helpers and convenience methods per component type. Pull request looks good as well, will merge once I have some spare time to prepare a release. At least no reason to suspect anything breaking in this case what I can see.
Also, another option could be to use the js version with the loop, and a separate .d.ts file next to it, overriding the api from react-native-svg
Thanks for the support, I am currently using the wrappers but don't exclude the other options for the future.