Skip to content
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

useImageDimensions #87

Merged
merged 22 commits into from
Apr 16, 2020
Merged
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ yarn add @react-native-community/hooks
- [useCameraRoll](https://github.com/react-native-community/hooks#usecameraroll)
- [useClipboard](https://github.com/react-native-community/hooks#useclipboard)
- [useDimensions](https://github.com/react-native-community/hooks#usedimensions)
- [useImageDimensions](https://github.com/react-native-community/hooks#useImageDimensions)
- [useKeyboard](https://github.com/react-native-community/hooks#usekeyboard)
- [useInteractionManager](https://github.com/react-native-community/hooks#useinteractionmanager)
- [useDeviceOrientation](https://github.com/react-native-community/hooks#usedeviceorientation)
Expand Down Expand Up @@ -104,6 +105,23 @@ const { width, height } = useDimensions().window
const screen = useDimensions().screen
```

### `useImageDimensions`

```js
import {useImageDimensions} from '@react-native-community/hooks'

const source = require('./assets/yourImage.png')
// or
const source = {uri: 'https://your.image.URI'}

const {dimensions, loading, error} = useImageDimensions(source)

if(loading || error || !dimensions) {
return null
}
const {width, height, aspectRatio} = dimensions
```

### `useKeyboard`

```js
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {useKeyboard} from './useKeyboard'
import {useInteractionManager} from './useInteractionManager'
import {useDeviceOrientation} from './useDeviceOrientation'
import {useLayout} from './useLayout'
import {useImageDimensions} from './useImageDimensions'

export {
useDimensions,
Expand All @@ -20,4 +21,5 @@ export {
useInteractionManager,
useDeviceOrientation,
useLayout,
useImageDimensions,
}
59 changes: 59 additions & 0 deletions src/useImageDimensions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import {useEffect, useState} from 'react'
import {Image, ImageRequireSource} from 'react-native'

export interface URISource {
LinusU marked this conversation as resolved.
Show resolved Hide resolved
uri: string
}

export type Source = ImageRequireSource | URISource
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you make one more type and export it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone wants to build a hook that extends on this hook, it's nice to be able to have one type that represents any input. That way it can easily be used as the input value to a function and just passed on, e.g:

function useHeaderHeight (logo: Source, background: Source): number {
  // ...
}


export interface ImageDimensions {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you also export this one?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If someone wants to build a hook that extends on this hook, it's nice to be able to have one type that represents the return value. That way it can easily be used as the return value to a function and just passed on, e.g:

function useLogoDimensions (): ImageDimensions {
  return useImageDimensions(globalSettings.logoImage)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TypeScript calculates return types automatically, there is no need to mark it manually.
Also, the 'ImageDimensions' name doesn't fully represent the content of this interface. There are 'error' and 'loading' fields. And the sizes themselves are even in a nested property 'dimensions', this field may be called 'ImageDimensions'. The whole object, which contains all of these - not really.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be open to renaming it ImageDimensionsResult or something similar...

TypeScript calculates return types automatically, there is no need to mark it manually.

Many style guides mandate that the return type be specified to avoid accidentally not retuning what you intended by clearly stating the intent up front. I think that's a good practice 👍

dimensions?: {width: number; height: number; aspectRatio: number}
error?: Error
loading: boolean
}

/**
* @param source either a remote URL or a local file resource.
* @returns original image dimensions (width, height and aspect ratio).
*/
export function useImageDimensions(source: Source): ImageDimensions {
const [result, setResult] = useState<ImageDimensions>({loading: true})

useEffect(() => {
try {
if (typeof source === 'number') {
const {width, height} = Image.resolveAssetSource(source)

setResult({
dimensions: {width, height, aspectRatio: width / height},
loading: false,
})

return
}

if (typeof source === 'object' && source.uri) {
setResult({loading: true})

Image.getSize(
source.uri,
(width, height) =>
setResult({
dimensions: {width, height, aspectRatio: width / height},
loading: false,
}),
error => setResult({error, loading: false}),
)

return
}

throw new Error('not implemented')
} catch (error) {
setResult({error, loading: false})
}
}, [source])
Copy link

@tuantranailo tuantranailo Jul 9, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A question here: if the source is an object, wouldn't it be changed every render, triggering the warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or **one of the dependencies changes on every render.**? This is because useEffect hook does shallow comparison, not deep comparison. @Greg-Bush and @LinusU

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends on how you pass in the source, as long as the same object is being passed each render cycle it will not trigger the useEffect to re-run. The one case I can see would be problematic is if people pass the url as such:

useImageDimensions({ uri: 'https://...' })

We could help with this by doing something like:

-  }, [source])
+  }, [source?.uri ?? source])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LinusU Yes. That would definitely do the trick. 👍 👍 👍


return result
}