Skip to content

Commit

Permalink
Merge pull request #593 from redbadger/gh-587-contentful-image-loadin…
Browse files Browse the repository at this point in the history
…g-resize

Optimize Contentful image loading
  • Loading branch information
Lily authored Jul 19, 2018
2 parents b2a8829 + d21a48c commit 80840a6
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 13 deletions.
52 changes: 43 additions & 9 deletions src/components/ImageBackground.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow
import React from "react";
import React, { Component } from "react";
import { ImageBackground as RNImageBackground } from "react-native";
import { connect } from "react-redux";
import type { Connector } from "react-redux";
import type { LayoutEvent } from "react-native/Libraries/Types/CoreEventTypes";
import type { ImageDetails } from "../data/image";
import { getImageDetails as createImageDetailsGetter } from "../data/image";
import type { FieldRef } from "../data/field-ref";
Expand All @@ -11,19 +12,52 @@ type OwnProps = {
reference: FieldRef
};

type State = {
imageSize: ?{ width: number, height: number }
};

type StateProps = {
getImageDetails: string => ?ImageDetails
getImageDetails: (string, { width: number, height: number }) => ?ImageDetails
};

type Props = OwnProps & StateProps;

export const ImageBackground = ({
reference,
getImageDetails,
...props
}: Props) => (
<RNImageBackground source={getImageDetails(reference.sys.id)} {...props} />
);
export class ImageBackground extends Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = { imageSize: null };
}

onLayout = (event: LayoutEvent) => {
this.setState({
imageSize: {
width: event.nativeEvent.layout.width,
height: event.nativeEvent.layout.height
}
});
};

render() {
const { reference, getImageDetails, ...props } = this.props;

// Single transparent pixel rendered initally so image size can be measured
const imageNotLoadedPlaceholder = {
uri:
"data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="
};

const imgSrc = this.state.imageSize
? getImageDetails(reference.sys.id, {
width: this.state.imageSize.width,
height: this.state.imageSize.height
})
: imageNotLoadedPlaceholder;

return (
<RNImageBackground onLayout={this.onLayout} source={imgSrc} {...props} />
);
}
}

// Note we must add a return type here for react-redux connect to work
// with flow correctly. If not provided is silently fails if types do
Expand Down
23 changes: 22 additions & 1 deletion src/components/ImageBackground.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { shallow } from "enzyme";
import { sampleOne, generateImageDetails } from "../data/__test-data";
import { ImageBackground } from "./ImageBackground";

it("renders correctly", () => {
it("renders placeholder image correctly", () => {
const getImageDetails = () => sampleOne(generateImageDetails);
const reference = { sys: { id: "a" } };
const output = shallow(
Expand All @@ -17,3 +17,24 @@ it("renders correctly", () => {
);
expect(output).toMatchSnapshot();
});

it("renders image correctly", () => {
const getImageDetails = jest.fn(() => sampleOne(generateImageDetails));
const reference = { sys: { id: "a" } };
const layoutDimensions = { width: 500, height: 200 };
const output = shallow(
<ImageBackground
getImageDetails={getImageDetails}
reference={reference}
resizeMode="contain"
accessibilityLabel="Test Label"
/>
);

output.instance().onLayout({ nativeEvent: { layout: layoutDimensions } });
output.update();

expect(getImageDetails).toHaveBeenCalledTimes(1);
expect(getImageDetails).toBeCalledWith(reference.sys.id, layoutDimensions);
expect(output).toMatchSnapshot();
});
16 changes: 15 additions & 1 deletion src/components/__snapshots__/ImageBackground.test.js.snap
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`renders correctly 1`] = `
exports[`renders image correctly 1`] = `
<ImageBackground
accessibilityLabel="Test Label"
onLayout={[Function]}
resizeMode="contain"
source={
Object {
Expand All @@ -15,3 +16,16 @@ exports[`renders correctly 1`] = `
}
/>
`;

exports[`renders placeholder image correctly 1`] = `
<ImageBackground
accessibilityLabel="Test Label"
onLayout={[Function]}
resizeMode="contain"
source={
Object {
"uri": "data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=",
}
}
/>
`;
19 changes: 17 additions & 2 deletions src/data/image.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,23 @@ export type ImageDetails = {
};

export const getImageDetails = (images: Images) => (
id: string
): ?ImageDetails => images[id];
id: string,
dimensions: ?{ width: number, height: number }
): ?ImageDetails => {
const imageDetails = images[id];

// Image details might be undefined in case of unpublished image
if (!dimensions || !imageDetails) {
return imageDetails;
}

const imageUriWithResizingBehaviourParameters = `${imageDetails.uri}?w=${
dimensions.width
}&h=${dimensions.height}&fit=fill`;
return Object.assign({}, imageDetails, {
uri: imageUriWithResizingBehaviourParameters
});
};

export const decodeImageDetails = (locale: string): Decoder<ImageDetails> =>
decode.shape({
Expand Down
28 changes: 28 additions & 0 deletions src/data/image.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,34 @@ describe("image", () => {

expect(getter(imageDetails.id)).toEqual(imageDetails);
});

it("returns the correct imageDetails when dimensions are provided", () => {
const imageDetails: ImageDetails = sampleOne(generateImageDetails);
const imageDimensions = { width: 500, height: 200 };
const imageDetailsWithDimensions = {
...imageDetails,
uri: `${imageDetails.uri}?w=${imageDimensions.width}&h=${
imageDimensions.height
}&fit=fill`
};

const getter = getImageDetails({
[imageDetails.id]: imageDetails
});

expect(getter(imageDetails.id, imageDimensions)).toEqual(
imageDetailsWithDimensions
);
});
});

it("returns correctly when dimensions are provided but there is no image details for image id", () => {
const imageDimensions = { width: 500, height: 200 };
const imageId = "image-id";

const getter = getImageDetails({});

expect(getter(imageId, imageDimensions)).toBeUndefined();
});

describe("decodeImageDetails", () => {
Expand Down

0 comments on commit 80840a6

Please sign in to comment.