-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle Invalid scale and arcDegreeScale values in segments to avoid crash #114
base: development
Are you sure you want to change the base?
Handle Invalid scale and arcDegreeScale values in segments to avoid crash #114
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I'll pull up and run the example app locally, couple of comments.
import { SegmentedArcError } from './segmentedArcErrors'; | ||
import WarningManager from './warningManager'; | ||
|
||
const isInvalidScalePercent = value => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since these are local functions can we _
underscore these?
|
||
export const createInvalidScaleValueError = (propertyName, value) => { | ||
return new SegmentedArcError( | ||
`Each '${propertyName}' value should be a positive number between 0 and 1 inclusive. It was found ${value}` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should paraphrase the ending something like this or better.
`Each '${propertyName}' value should be a positive number between 0 and 1 inclusive. It was found ${value}` | |
`Each '${propertyName}' value should be a positive number between 0 and 1 inclusive. ${value} was provided instead` |
|
||
if (!warnings.has(key)) { | ||
console.warn(data); | ||
warnings.add(key); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to clarify, did we opt into having a warning set becase we wanted to avoid showing the same warnings more than once?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was to avoid depending on the useShowSegmentedArcWarnings
useRef to just show the same warning once in case rerender happens, like when an array is passed directly as props without memoization, and avoid spamming multiple warnings per render. But I'll check if that is still needed
<SegmentedArc segments={[ {scale: NaN} ]} />
@@ -0,0 +1,29 @@ | |||
export const ensureDefaultSegmentScale = segments => { | |||
const isInvalidScale = value => !Number.isFinite(value) || value === 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we move this function to outside of ensureDefaultSegmentScale
function?
const allocatedScale = segments.reduce((sum, { scale }) => sum + (isInvalidScale(scale) ? 0 : scale), 0); | ||
const defaultArcScale = invalidScaleCount > 0 ? (1 - allocatedScale) / invalidScaleCount : 0; | ||
|
||
return segments.map(segment => (isInvalidScale(segment.scale) ? { ...segment, scale: defaultArcScale } : segment)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Appreaciate for not manipulating props directly!
}; | ||
|
||
export const ensureDefaultSegmentArcDegreeScale = segments => { | ||
const isInvalidArcDegreeScale = value => !Number.isFinite(value); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we also move this to outside of this function and make it _local
?
global.__DEV__ = currentGlobalDev; | ||
}); | ||
|
||
it('show warnings and renders the component when segments have invalid scale or arcDegreeScale data', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
adding s
it('show warnings and renders the component when segments have invalid scale or arcDegreeScale data', () => { | |
it('shows warnings and renders the component when segments have invalid scale or arcDegreeScale data', () => { |
|
||
const segmentsResult = ensureDefaultSegmentArcDegreeScale(segments); | ||
|
||
expect(segmentsResult).toEqual([{ arcDegreeScale: 1 / 2 }, { arcDegreeScale: 1 / 2 }]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
did you just use 1/2
to avoid precision issues?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. it's 1/2 because there're two segments in here const segments = [{ arcDegreeScale: NaN }, { arcDegreeScale: NaN }];
so the default value would be 1 / numberOfSegments. I'll change to 1 / segments.length
to make it clear
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wanted to say could we do 0.5 ?
{ | ||
arcDegreeScale: 8 / 50 | ||
}, | ||
{ | ||
arcDegreeScale: 42 / 50 | ||
} | ||
]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nitpick it seems like having exact values like this is more readable
{ | |
arcDegreeScale: 8 / 50 | |
}, | |
{ | |
arcDegreeScale: 42 / 50 | |
} | |
]) | |
{ | |
arcDegreeScale: 0.16 | |
}, | |
{ | |
arcDegreeScale: 0.84 | |
} | |
]) |
describe('ensureDefaultSegmentScaleValues', () => { | ||
it('ensures default values for `scale` are set', () => { | ||
expect( | ||
ensureDefaultSegmentScaleValues([ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe we could have called this outside of expect function and used a variable instead.
Description
Currently, when an invalid number (NaN) is passed as
arcDegreeScale
, Android crashes, and iOS generates an inconsistent graph.Solution:
If the
arcDegreeScale
orscale
is a finite number, parse it as usual. However, if either value is invalid (i.e., NaN, Infinity, or -Infinity, or any unexpected numeric value), issue a warning to the user and override the value with a default scale. This default will be calculated based on the remaining value needed to sum to 1 (current solution already). This ensures that neitherarcDegreeScale
norscale
will ever be NaN, preventing crashes.Issue a warning every time a SegmentArc is rendered for the first time, under the following conditions:
arcDegreeScale
orscale
is not a finite number (i.e., NaN, Infinity, or -Infinity).arcDegreeScale
orscale
value is outside the valid range of 0 (0%) to 1 (100%).scale
orarcDegreeScale
values exceeds 1 or is less than 0.Obs:
Previously, the scale and arcDegreeScale props were being mutated, which is generally discouraged by eslint and considered difficult to debug. With this change, the same behavior will be maintained without mutating the props. Note that this will require some snapshot updates. If this mutation is wanted for some reason, let me know to undo.
In the current solution:
scale
value of zero is considered invalid, so it is overridden by_ensureDefaultSegmentScale
. This behavior has been preserved.arcDegreeScale
value of zero is considered valid, so it is not overridden by_ensureDefaultArcScale
. This logic remains unchanged.scale
orarcDegreeScale
values set toundefined
are currently accepted as valid props. The previous functions_ensureDefaultSegmentScale
and_ensureDefaultArcScale
handle these values by evenly distributing the value across segments, so no warning is generated for these cases, and the same logic is preserved.Screenshots
Android
android_before.mp4
android_after.mp4
iOS
ios_before.mp4
ios_after.mp4
No warnings for valid props
Some possible warnings
arcDegreeScale
scale