Skip to content

Commit

Permalink
Merge pull request #1 from netceteragroup/hooks
Browse files Browse the repository at this point in the history
Rewrite with Hooks
  • Loading branch information
oliverlaz committed Mar 16, 2019
2 parents d5ccbcf + 0ffce95 commit 8296888
Show file tree
Hide file tree
Showing 16 changed files with 449 additions and 294 deletions.
5 changes: 4 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,15 @@
"prettier"
],
"plugins": [
"prettier"
"prettier",
"react-hooks"
],
"parser": "babel-eslint",
"rules": {
"import/prefer-default-export": "off",
"prettier/prettier": "error",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"react/destructuring-assignment": "off",
"react/jsx-filename-extension": "off",
"react/jsx-one-expression-per-line": "off",
Expand Down
40 changes: 33 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,19 @@ An optional fetching lifecycle method. Invoked just after `GET /props.url` respo
##### `onFetchingError?: (e?: Error) => void`
An optional fetching lifecycle method. Invoked when error occurs during fetching/processing stage.

## `useMessageSource(keyPrefix?: string): ComponentAPI`
A React Hook version of the [ComponentAPI](#ComponentApi).

```js
import React from 'react'
import { useMessageSource } from 'react-message-source'

function MyComponent() {
const { getMessage, getMessageWithNamedParams } = useMessageSource()
return ...
}
```

## `withMessages`
Creates a higher order component and provides the [ComponentAPI](#ComponentAPI) as `props`. It can be used in two ways:

Expand Down Expand Up @@ -102,7 +115,7 @@ Exposes the [ComponentAPI](#ComponentApi) as standard `prop-types` definition.
#### `App.jsx`
```jsx
import React, { Component } from 'react'
import * as MessageSource from 'react-message-source'
import { Provider as MessageSourceProvider } from 'react-message-source'

import translations from './translations.json'

Expand All @@ -112,31 +125,33 @@ import MyComponentWithNamedParams from './MyComponentWithNamedParams'

export default function App() {
return (
<MessageSource.Provider value={translations}>
<MessageSourceProvider value={translations}>
<MyComponent />
<MyComponentWithHooks />
<MyComponentWithIndexedParams />
<MyComponentWithNamedParams />
</MessageSource.Provider>
</MessageSourceProvider>
)
}
```

#### `FetchApp.jsx`
```jsx
import React, { Component } from 'react'
import * as MessageSource from 'react-message-source'
import { FetchingProvider as FetchingMessageSourceProvider } from 'react-message-source'

import MyComponent from './MyComponent'
import MyComponentWithIndexedParams from './MyComponentWithIndexedParams'
import MyComponentWithNamedParams from './MyComponentWithNamedParams'

export default function FetchApp() {
return (
<MessageSource.FetchingProvider url="http://api.myapp.com/intl?lang=en">
<FetchingMessageSourceProvider url="http://api.myapp.com/intl?lang=en">
<MyComponent />
<MyComponentWithHooks />
<MyComponentWithIndexedParams />
<MyComponentWithNamedParams />
</MessageSource.FetchingProvider>
</FetchingMessageSourceProvider>
)
}
```
Expand All @@ -154,6 +169,17 @@ function MyComponent(props) {
export default withMessages(MyComponent)
```

#### `MyComponentWithHooks.jsx`
```jsx
import React from 'react'
import { useMessageSource } from 'react-message-source'

export default function MyComponent(props) {
const { getMessage } = useMessageSource();
return <span>{getMessage('hello.world')}</span>
}
```

#### `MyComponentWithIndexedParams.jsx`
```jsx
import React from 'react'
Expand Down Expand Up @@ -187,4 +213,4 @@ export default compose(

## License

MIT © [Netcetera AG](https://github.com/netceteragroup)
MIT © [Netcetera](https://github.com/netceteragroup)
11 changes: 5 additions & 6 deletions example/src/App.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React from 'react';
import * as MessageSource from 'react-message-source';
import { Provider as MessageSourceProvider } from 'react-message-source';

import Hooks from './Hooks';
import {
LocalizedLabel,
LocalizedLabelCurried,
Expand All @@ -18,15 +19,13 @@ export default function App() {
<React.Fragment>
<p>The content below is localized, see Greeting.js for more information.</p>

<MessageSource.Provider value={translations}>
<MessageSourceProvider value={translations}>
<LocalizedLabel />

<LocalizedLabelCurried />

<PrefixedLocalizedLabel />

<LocalizedLabelWithNamedParams />
</MessageSource.Provider>
<Hooks />
</MessageSourceProvider>
</React.Fragment>
);
}
7 changes: 7 additions & 0 deletions example/src/Hooks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import React from 'react';
import { useMessageSource } from 'react-message-source';

export default function Hooks() {
const { getMessage } = useMessageSource();
return <span>Translation with a hook: {getMessage('hello.world')}</span>;
}
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
},
"peerDependencies": {
"prop-types": "^15.5.10",
"react": "^16.6.0"
"react": "^16.8.0"
},
"devDependencies": {
"@babel/core": "^7.3.4",
Expand All @@ -49,6 +49,7 @@
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-prettier": "^3.0.1",
"eslint-plugin-react": "^7.12.4",
"eslint-plugin-react-hooks": "^1.5.1",
"prettier": "^1.16.4",
"prop-types": "^15.7.2",
"react": "^16.8.4",
Expand Down
5 changes: 4 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/**
* The Public API.
*/
export { Provider, withMessages, propTypes } from './lib/messageSource';
export { Provider, Consumer } from './lib/MessageSourceContext';
export { FetchingProvider } from './lib/FetchingProvider';
export { useMessageSource } from './lib/useMessageSource';
export { withMessages } from './lib/withMessages';
export { propTypes } from './lib/propTypes';
191 changes: 90 additions & 101 deletions src/lib/FetchingProvider.js
Original file line number Diff line number Diff line change
@@ -1,112 +1,101 @@
import React, { Component } from 'react';
import React from 'react';
import PropTypes from 'prop-types';

import { Provider } from './messageSource';
import { Provider } from './MessageSourceContext';

const identity = x => x;

export class FetchingProvider extends Component {
state = {
translations: {},
fetching: false,
};

mounted = false;

static propTypes = {
/**
* The URL which serves the text messages.
* Required.
*/
url: PropTypes.string.isRequired,

/**
* Makes the rendering of the sub-tree synchronous.
* The components will not render until fetching of the text messages finish.
*
* Defaults to true.
*/
blocking: PropTypes.bool,

/**
* A function which can transform the response received from GET /props.url
* to a suitable format:
*
* Example:
* function transform(response) {
* return response.textMessages;
* }
*/
transform: PropTypes.func,

/**
* Invoked when fetching of text messages starts.
*/
onFetchingStart: PropTypes.func,

/**
* Invoked when fetching of text messages finishes.
*/
onFetchingEnd: PropTypes.func,

/**
* Invoked when fetching fails.
*/
onFetchingError: PropTypes.func,

/**
* Children.
*/
children: PropTypes.node,
};

static defaultProps = {
blocking: true,
transform: identity,
onFetchingStart: identity,
onFetchingEnd: identity,
onFetchingError: identity,
};

componentDidMount() {
this.mounted = true;
this.fetchResources();
}

componentDidUpdate(prevProps) {
if (this.props.url !== prevProps.url) {
this.fetchResources();
}
}

componentWillUnmount() {
this.mounted = false;
}

fetchResources = () => {
const { url, transform, onFetchingStart, onFetchingEnd, onFetchingError } = this.props;

this.setState({ fetching: true }, onFetchingStart);
const initialState = {
translations: {},
isFetching: false,
};

/**
* A special <Provider /> which can load translations from remote URL
* via a `GET` request and pass them down the component tree.
*/
export function FetchingProvider(props) {
const { url, blocking, children, transform, onFetchingStart, onFetchingEnd, onFetchingError } = props;
const [{ translations, isFetching }, setState] = React.useState(initialState);

React.useEffect(() => {
let isStillMounted = true;

setState(state => ({ ...state, isFetching: true }));
onFetchingStart();

fetch(url)
.then(r => r.json())
.then(response => {
if (this.mounted) {
this.setState(
{
translations: transform(response),
fetching: false,
},
onFetchingEnd,
);
if (isStillMounted) {
setState({
translations: transform(response),
isFetching: false,
});
onFetchingEnd();
}
})
.catch(onFetchingError);
};

render() {
const { blocking, children } = this.props;
const { translations, fetching } = this.state;
const shouldRenderSubtree = !blocking || (blocking && !fetching);
return <Provider value={translations}>{shouldRenderSubtree ? children : null}</Provider>;
}

return () => {
isStillMounted = false;
};
}, [url]); // re-fetch only when url changes

const shouldRenderSubtree = !blocking || (blocking && !isFetching);
return <Provider value={translations}>{shouldRenderSubtree ? children : null}</Provider>;
}

FetchingProvider.propTypes = {
/**
* The URL which serves the text messages.
* Required.
*/
url: PropTypes.string.isRequired,

/**
* Makes the rendering of the sub-tree synchronous.
* The components will not render until fetching of the text messages finish.
*
* Defaults to true.
*/
blocking: PropTypes.bool,

/**
* A function which can transform the response received from GET /props.url
* to a suitable format:
*
* Example:
* function transform(response) {
* return response.textMessages;
* }
*/
transform: PropTypes.func,

/**
* Invoked when fetching of text messages starts.
*/
onFetchingStart: PropTypes.func,

/**
* Invoked when fetching of text messages finishes.
*/
onFetchingEnd: PropTypes.func,

/**
* Invoked when fetching fails.
*/
onFetchingError: PropTypes.func,

/**
* Children.
*/
children: PropTypes.node,
};

FetchingProvider.defaultProps = {
blocking: true,
transform: identity,
onFetchingStart: identity,
onFetchingEnd: identity,
onFetchingError: identity,
};

0 comments on commit 8296888

Please sign in to comment.