Skip to content

Commit c46ef0a

Browse files
committed
**feat**: provide svg props in onload
The SvgProps are now provided to the onLoad handler of the SvgUri and SvgCssUri components. Then we can react on different aspects of the SVG when loaded. Background: Had an issue with SVGs not scaling correctly when changing the height of the image. With the handler I could fix my problem on my app side. I can imagine other use cases for this feature and decided to contribute this change here. This PR does not fix the issue I had, but adds the feature for the onLoad handler. Any ideas to fix the issue itself are warmly welcome. The issue is described in the PR's test case. Other considerations in this PR: - using useMemo or UseEffect in conditionals/try-catch is not recommended, so restyled this code structure.
1 parent 15536f7 commit c46ef0a

File tree

5 files changed

+233
-25
lines changed

5 files changed

+233
-25
lines changed

USAGE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,15 @@ If remote SVG file contains CSS in `<style>` element, use `SvgCssUri`:
9292
import * as React from 'react';
9393
import { ActivityIndicator, View, StyleSheet } from 'react-native';
9494
import { SvgCssUri } from 'react-native-svg/css';
95+
import type { SvgProps } from 'react-native-svg';
9596
export default function TestComponent() {
9697
const [loading, setLoading] = React.useState(true);
9798
const onError = (e: Error) => {
9899
console.log(e.message);
99100
setLoading(false);
100101
};
101-
const onLoad = () => {
102-
console.log('Svg loaded!');
102+
const onLoad = (svgProps: SvgProps) => {
103+
console.log('Svg loaded!', svgProps);
103104
setLoading(false);
104105
};
105106
return (

apps/common/test/SvgUriOnLoad.tsx

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
import React, {useState} from 'react';
2+
import {View, Text} from 'react-native';
3+
import {SvgProps, SvgUri} from '../../../src';
4+
import {SvgCssUri} from 'react-native-svg/css';
5+
6+
const svgDataUri =
7+
'data:image/svg+xml;base64,' +
8+
btoa(`
9+
<svg width="60" height="60" viewBox="0 0 60 60" xmlns="http://www.w3.org/2000/svg" fill="pink">
10+
<ellipse cx="30" cy="30" rx="30" ry="15" fill="yellow" />
11+
</svg>
12+
`);
13+
14+
const cssSvgDataUri =
15+
'data:image/svg+xml;base64,' +
16+
btoa(`
17+
<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
18+
<style>
19+
.small { font: italic 13px sans-serif; }
20+
</style>
21+
<rect x="0" y="0" width="100" height="100" fill="yellow" />
22+
<text x="20" y="35" class="small">CSS Styled</text>
23+
</svg>
24+
`);
25+
26+
const calculateWidth = (svgProps: SvgProps, desiredHeight: number) => {
27+
let originalHeight = svgProps.height;
28+
let originalWidth = svgProps.width;
29+
if (originalWidth && originalHeight) {
30+
return (Number(originalWidth) / Number(originalHeight)) * desiredHeight;
31+
}
32+
};
33+
34+
const ExampleWithSvgUri = () => {
35+
const [width, setWidth] = useState(0);
36+
const height = 12;
37+
38+
const handleLoad = (svgProps: SvgProps) => {
39+
const calculatedWidth = calculateWidth(svgProps, height);
40+
if (calculatedWidth) {
41+
setWidth(calculatedWidth);
42+
}
43+
};
44+
return (
45+
<View style={{flex: 1}}>
46+
<Text style={{fontSize: 24, color: '#fff'}}>SvgUri component</Text>
47+
48+
<Text style={{marginTop: 10, color: '#fff'}}>Original Size ✅</Text>
49+
<SvgUri style={{backgroundColor: 'blue'}} uri={svgDataUri} />
50+
51+
<Text style={{marginTop: 10, color: '#fff'}}>
52+
Set different height ❌
53+
</Text>
54+
<SvgUri
55+
style={{backgroundColor: 'red'}}
56+
uri={svgDataUri}
57+
height={height}
58+
/>
59+
60+
<Text style={{marginTop: 10, color: '#fff'}}>
61+
Preserve Aspect Ratio Meet ❌
62+
</Text>
63+
<SvgUri
64+
style={{backgroundColor: 'red'}}
65+
uri={svgDataUri}
66+
height={height}
67+
preserveAspectRatio="xMinYMid meet"
68+
/>
69+
70+
<Text style={{marginTop: 10, color: '#fff'}}>
71+
Preserve Aspect Ratio Slice ❌
72+
</Text>
73+
<SvgUri
74+
style={{backgroundColor: 'red'}}
75+
uri={svgDataUri}
76+
height={height}
77+
preserveAspectRatio="xMinYMid slice"
78+
/>
79+
80+
<Text style={{marginTop: 10, color: '#fff'}}>
81+
Preserve Aspect Ratio None ❌
82+
</Text>
83+
<SvgUri
84+
style={{backgroundColor: 'red'}}
85+
uri={svgDataUri}
86+
height={height}
87+
preserveAspectRatio="none"
88+
/>
89+
90+
<Text style={{marginTop: 10, color: '#fff'}}>
91+
Auto calculate width ✅
92+
</Text>
93+
<SvgUri
94+
style={{backgroundColor: 'green'}}
95+
uri={svgDataUri}
96+
height={height}
97+
width={width}
98+
onLoad={handleLoad}
99+
/>
100+
</View>
101+
);
102+
};
103+
104+
const ExampleWithSvgCssUri = () => {
105+
const [width, setWidth] = useState(0);
106+
const height = 25;
107+
const handleLoad = (svgProps: SvgProps) => {
108+
const calculatedWidth = calculateWidth(svgProps, height);
109+
if (calculatedWidth) {
110+
setWidth(calculatedWidth);
111+
}
112+
};
113+
114+
return (
115+
<View style={{flex: 1}}>
116+
<Text style={{fontSize: 24, color: '#fff'}}>SvgCssUri component</Text>
117+
118+
<Text style={{marginTop: 10, color: '#fff'}}>Original Size ✅</Text>
119+
<SvgCssUri style={{backgroundColor: 'blue'}} uri={cssSvgDataUri} />
120+
121+
<Text style={{marginTop: 10, color: '#fff'}}>
122+
Set different height ❌
123+
</Text>
124+
<SvgCssUri
125+
style={{backgroundColor: 'red'}}
126+
height={height}
127+
uri={cssSvgDataUri}
128+
/>
129+
130+
<Text style={{marginTop: 10, color: '#fff'}}>
131+
Preserve Aspect Ratio Meet ❌
132+
</Text>
133+
<SvgCssUri
134+
style={{backgroundColor: 'red'}}
135+
height={height}
136+
uri={cssSvgDataUri}
137+
preserveAspectRatio="xMinYMid meet"
138+
/>
139+
140+
<Text style={{marginTop: 10, color: '#fff'}}>
141+
Preserve Aspect Ratio Slice ❌
142+
</Text>
143+
<SvgCssUri
144+
style={{backgroundColor: 'red'}}
145+
height={height}
146+
uri={cssSvgDataUri}
147+
preserveAspectRatio="xMinYMid slice"
148+
/>
149+
150+
<Text style={{marginTop: 10, color: '#fff'}}>
151+
Preserve Aspect Ratio None ❌
152+
</Text>
153+
<SvgCssUri
154+
style={{backgroundColor: 'red'}}
155+
height={height}
156+
uri={cssSvgDataUri}
157+
preserveAspectRatio="none"
158+
/>
159+
160+
<Text style={{marginTop: 10, color: '#fff'}}>
161+
Auto calculate width ✅
162+
</Text>
163+
<SvgCssUri
164+
style={{backgroundColor: 'green'}}
165+
height={height}
166+
uri={cssSvgDataUri}
167+
width={width}
168+
onLoad={handleLoad}
169+
/>
170+
</View>
171+
);
172+
};
173+
174+
export default () => {
175+
return (
176+
<View style={{flex: 1, padding: 10, flexDirection: 'row'}}>
177+
<ExampleWithSvgUri />
178+
<ExampleWithSvgCssUri />
179+
</View>
180+
);
181+
};

apps/common/test/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import Test2417 from './Test2417';
3535
import Test2455 from './Test2455';
3636
import Test2471 from './Test2471';
3737
import Test2520 from './Test2520';
38+
import Test2540 from './SvgUriOnLoad';
3839
import Test2670 from './Test2670';
3940

4041
export default function App() {

src/css/css.tsx

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -766,17 +766,29 @@ export const inlineStyles: Middleware = function inlineStyles(
766766
};
767767

768768
export function SvgCss(props: XmlProps) {
769-
const { xml, override, fallback, onError = err } = props;
770-
try {
771-
const ast = useMemo<JsxAST | null>(
772-
() => (xml !== null ? parse(xml, inlineStyles) : null),
773-
[xml]
774-
);
775-
return <SvgAst ast={ast} override={override || props} />;
776-
} catch (error) {
777-
onError(error);
769+
const { xml, override, fallback, onError = err, onLoad } = props;
770+
771+
const [ast, error] = useMemo<[JsxAST | null, unknown]>(() => {
772+
try {
773+
const parsed = xml !== null ? parse(xml, inlineStyles) : null;
774+
return [parsed, null];
775+
} catch (exc) {
776+
return [null, exc];
777+
}
778+
}, [xml]);
779+
780+
useEffect(() => {
781+
if (!error && ast?.props) {
782+
onLoad?.(ast.props);
783+
} else if (error) {
784+
onError(error as Error);
785+
}
786+
}, [ast, error, onLoad, onError]);
787+
788+
if (error) {
778789
return fallback ?? null;
779790
}
791+
return <SvgAst ast={ast} override={override || props} />;
780792
}
781793

782794
export function SvgCssUri(props: UriProps) {
@@ -788,7 +800,6 @@ export function SvgCssUri(props: UriProps) {
788800
? fetchText(uri)
789801
.then((data) => {
790802
setXml(data);
791-
onLoad?.();
792803
})
793804
.catch((e) => {
794805
onError(e);
@@ -799,7 +810,9 @@ export function SvgCssUri(props: UriProps) {
799810
if (isError) {
800811
return fallback ?? null;
801812
}
802-
return <SvgCss xml={xml} override={props} fallback={fallback} />;
813+
return (
814+
<SvgCss xml={xml} override={props} fallback={fallback} onLoad={onLoad} />
815+
);
803816
}
804817

805818
// Extending Component is required for Animated support.

src/xml.tsx

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export interface JsxAST extends AST {
3535
export type AdditionalProps = {
3636
onError?: (error: Error) => void;
3737
override?: object;
38-
onLoad?: () => void;
38+
onLoad?: (svgProps: SvgProps) => void;
3939
fallback?: JSX.Element;
4040
};
4141

@@ -65,18 +65,29 @@ export function SvgAst({ ast, override }: AstProps) {
6565
const err = console.error.bind(console);
6666

6767
export function SvgXml(props: XmlProps) {
68-
const { onError = err, xml, override, fallback } = props;
68+
const { onError = err, xml, override, fallback, onLoad } = props;
6969

70-
try {
71-
const ast = useMemo<JsxAST | null>(
72-
() => (xml !== null ? parse(xml) : null),
73-
[xml]
74-
);
75-
return <SvgAst ast={ast} override={override || props} />;
76-
} catch (error) {
77-
onError(error);
70+
const [ast, error] = useMemo<[JsxAST | null, unknown]>(() => {
71+
try {
72+
const parsed = xml !== null ? parse(xml) : null;
73+
return [parsed, null];
74+
} catch (exc) {
75+
return [null, exc];
76+
}
77+
}, [xml]);
78+
79+
useEffect(() => {
80+
if (!error && ast?.props) {
81+
onLoad?.(ast.props);
82+
} else if (error) {
83+
onError(error as Error);
84+
}
85+
}, [ast, error, onError, onLoad]);
86+
87+
if (error) {
7888
return fallback ?? null;
7989
}
90+
return <SvgAst ast={ast} override={override || props} />;
8091
}
8192

8293
export function SvgUri(props: UriProps) {
@@ -89,7 +100,6 @@ export function SvgUri(props: UriProps) {
89100
.then((data) => {
90101
setXml(data);
91102
isError && setIsError(false);
92-
onLoad?.();
93103
})
94104
.catch((e) => {
95105
onError(e);
@@ -101,7 +111,9 @@ export function SvgUri(props: UriProps) {
101111
if (isError) {
102112
return fallback ?? null;
103113
}
104-
return <SvgXml xml={xml} override={props} fallback={fallback} />;
114+
return (
115+
<SvgXml xml={xml} override={props} fallback={fallback} onLoad={onLoad} />
116+
);
105117
}
106118

107119
// Extending Component is required for Animated support.

0 commit comments

Comments
 (0)