Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 0 additions & 2 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ The concept is very similar to `Provider` component from `react-redux`.

* `onError?: (error: Error) => void` (optional) - Error handler, called if the palette failed to create.

* `onInit?: () => void` - (optional) - Init handler, called when the `MaterialPaletteProvider` is just about to start creating the palette.

* `onFinish?: (palette: PaletteInstance) => void` - (optional) - Finish handler, called when the palette is created, but before it gets propagated to _connected_ components - use it, if you want to mutate the palette instance. If some profiles are not available for the provided image, the defaults will apply, taking precedence the ones you passed to the component as `this.props.defaults`.

* `children: React$Element<*>`, - (__required__) - Children elements - the rest of your app's component tree.
Expand Down
82 changes: 38 additions & 44 deletions src/PaletteProvider.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,6 @@ type Props = {
* Error handler, called when palette generation fails
*/
onError?: (error: Error) => void,
/**
* Initialization handler, called right before generation the palette
*/
onInit?: () => void,
/**
* Finish handler, called right after the palette is generated
*/
Expand All @@ -54,42 +50,28 @@ type Props = {
/**
* Render LoaderComponent when the palette is being created
*/
LoaderComponent?:
| React$Component<*, *, *>
| ((...args: *) => React$Element<*>),
LoaderComponent?: ReactClass<*> | ((...args: Array<*>) => React.Element<*>),
};

type State = {
palette: ?PaletteInstance,
};

function execIfFunction(possibleFunction: mixed, ...args: *): boolean {
if (typeof possibleFunction === 'function') {
possibleFunction(...args);
return true;
}
return false;
}

/**
* Provides broadcast for material palette instance via context.
* Passes `subscribe` method via context, which `withPalette` can call
* and subscribe in order to receive the palette instance.
*/
export default class MaterialPaletteProvider
extends Component<void, Props, State> {
state: State;

constructor(props: Props) {
super(props);
this.state = {
palette: null,
};
}
static childContextTypes = {
[KEY]: PropTypes.func.isRequired,
};

state: State = {
palette: null,
};

eventEmitter = createEventEmitter(null);

getChildContext() {
Expand Down Expand Up @@ -144,35 +126,47 @@ export default class MaterialPaletteProvider
if (this.props.defaults) {
validateDefaults(this.props.defaults);
}
execIfFunction(this.props.onInit);
createMaterialPalette(this.props.image, this.props.options)
.then((palette: PaletteInstance) => {

this._createPalette();
}

_createPalette = () => {
const { image, options, onFinish, onError } = this.props;

createMaterialPalette(image, options).then(
palette => {
const paletteWithDefaults = this._mergeWithDefaults(palette);
execIfFunction(this.props.onFinish, paletteWithDefaults);
if (!this.props.forceRender) {
this.setState({ palette: paletteWithDefaults });
}

if (onFinish) onFinish(paletteWithDefaults);

this.eventEmitter.publish({
palette: paletteWithDefaults,
});
})
.catch((error: Error) => {
const isCalled = execIfFunction(this.props.onError, error);
if (!isCalled) {
const enhancedError = error;
enhancedError.message = `Uncaught MaterialPaletteProvider exception: ${enhancedError.message}`;
throw enhancedError;
this.setState({ palette: paletteWithDefaults });
},
error => {
if (onError) {
onError(error);
} else {
error.message = `Uncaught MaterialPaletteProvider exception: ${error.message}`; // eslint-disable-line no-param-reassign
throw error;
}
});
}
},
);
};

render() {
if (!this.state.palette && this.props.LoaderComponent) {
return <this.props.LoaderComponent />;
} else if (!this.state.palette && !this.props.forceRender) {
return null;
const { forceRender, LoaderComponent, children } = this.props;
const shouldRender = this.state.palette || forceRender;

if (LoaderComponent && !this.state.palette) {
return <LoaderComponent />;
}

if (shouldRender) {
Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't we change the order? If forceRender is provided, it seems natural for me to override LoaderComponent, what do you think?

Copy link
Member Author

Choose a reason for hiding this comment

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

Just following what's in the comment in the prop types. cc @zamotany

Copy link
Contributor

Choose a reason for hiding this comment

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

From the docs: Does not take effect if LoaderComponent is specified!

Besides, why would you specify LoaderComponent when you would force the render anyway?

return React.Children.only(children);
}

return React.Children.only(this.props.children);
return null;
}
}
45 changes: 19 additions & 26 deletions src/__tests__/PaletteProvider.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('PaletteProvider', () => {
createMaterialPalette.mockReset();
});

it('should create palette and call `onInit` and `onFinish` handlers', done => {
it('should create palette and call `onFinish` handler when done', done => {
createMaterialPalette.mockImplementation(() =>
Promise.resolve({ vibrant: null }));

Expand Down Expand Up @@ -102,33 +102,26 @@ describe('PaletteProvider', () => {
);
});

it('should throw error if `onError` was not passed and palette creation fails', () =>
new Promise(resolve => {
function checkErrorAndFinish(error) {
expect(error.message).toMatch(/MaterialPaletteProvider.*test/);
resolve();
}

createMaterialPalette.mockImplementation(() => ({
then() {
return this;
},
catch(errorHandler) {
try {
errorHandler(new Error('test'));
} catch (error) {
checkErrorAndFinish(error);
}
},
}));

render(
<PaletteProvider image={0} options={{ type: 'vibrant' }}>
<Text>Test</Text>
</PaletteProvider>,
);
it('should throw error if `onError` was not passed and palette creation fails', () => {
createMaterialPalette.mockImplementation(() => ({
then(success, failure) {
try {
failure(new Error('test'));
} catch (error) {
expect(error.message).toBe(
'Uncaught MaterialPaletteProvider exception: test',
);
}
},
}));

render(
<PaletteProvider image={0} options={{ type: 'vibrant' }}>
<Text>Test</Text>
</PaletteProvider>,
);
});

it('should render children if `forceRender` is true when creating palette', done => {
createMaterialPalette.mockImplementation(
() =>
Expand Down
13 changes: 6 additions & 7 deletions src/__tests__/withPalette.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React from 'react';
import { shallow } from 'enzyme';
import { StyleSheet } from 'react-native';
import withPalette from '../withPalette';
import { KEY } from '../PaletteProvider';

Expand Down Expand Up @@ -159,19 +160,17 @@ describe('withPalette', () => {
expect(palette).toEqual({
lightVibrant: { ...localDefaults.lightVibrant, population: 0 },
});
expect(style).toEqual([{ fontSize: '14px' }, {}]);
expect(StyleSheet.flatten(style)).toEqual({ fontSize: '14px' });
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice! Didn't know about flatten 👍

}
function onSecondRender(palette, style) {
expect(palette).toEqual({
...paletteMock,
lightVibrant: { population: 0, ...localDefaults.lightVibrant },
});
expect(style).toEqual([
{ fontSize: '14px' },
{
color: localDefaults.lightVibrant.color,
},
]);
expect(StyleSheet.flatten(style)).toEqual({
fontSize: '14px',
color: localDefaults.lightVibrant.color,
});
}

const PaletteTest = withPalette(
Expand Down
5 changes: 1 addition & 4 deletions src/withPalette.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,7 @@ export default function withMaterialPalette(
return (
<WrappedComponent
{...rest}
style={[
...(Array.isArray(style) ? style : [style]),
stylesFromPalette,
]}
style={[style, stylesFromPalette]}
palette={palette}
/>
);
Expand Down