npm package: @psenger/react-static-config-loader
Description: React Static Config Loader, a convenience tag providing a widely used pattern of loading a static configuration from a server and using ReactJS Context, providing the cofnig to the context of components within the hiearchy in a clean and consistent manner
Author: Philip A Senger
Latest version: 1.0.5

# react-static-config-loader

> React Static Config Loader, a convenience component providing a widely used pattern of loading a
> static configuration from a server and injecting the configuration into the ReactJS Context,
> providing the config within the hierarchy in a clean and consistent manner.

Ideally, the best possible solution would be to bundle a configuration with the build. However,
for many, this is not practical.

The react-static-config-loader component, utilizes ReactJS Context, and injects a value ( a
configuration ) into React Classes. Unfortunately ( as of React 16 ) is incapable of inject it into
JSX functions ( which make sense as JSX are intended to be "Pure" ). This component does provide
prebuilt object that can overcome this by creating HOC's and injecting the context values into the
props.

<!--START_SECTION:toc-->

## Table of contents
- [react-static-config-loader](#react-static-config-loader)
  * [Install](#install)
  * [Usage](#usage)
  * [API](#api)
    + [Context](#context)
    + [Provider](#provider)
    + [Consumer](#consumer)
    + [ConfigPropExtenderHoc](#configpropextenderhoc)
      - [Parameters](#parameters)
      - [Examples](#examples)
    + [loaderCall](#loadercall)
    + [StaticConfigWrapper](#staticconfigwrapper)
      - [Parameters](#parameters-1)
      - [Examples](#examples-1)
  * [Contributing](#contributing)
    + [Rules](#rules)
  * [Deployment Steps](#deployment-steps)
  * [License](#license)

<!--END_SECTION:toc-->

<!--START_SECTION:file:INSTALL.md-->
## Install

This project, hosted alternatively in GitHub, not NPM, requires you append the following to a project level file `./.npmrc`

```
@psenger:registry=https://npm.pkg.github.com
```

Once completed, you can then execute either `npm` or `yarn` to install.

```bash
npm install @psenger/react-static-config-loader --save
```

<!--END_SECTION:file:INSTALL.md-->

<!--START_SECTION:file:TUTORIAL.md-->
## Usage

In the simplest example, we want to simply fetch a configuration json file from the server and send the
`config` into the ReactJS context of the children. The async function ( `fn` in this example ) is passed as a property
called `loader`.

While loading ( and default behaviour ), any JSX passed to `loadingMsg` will be called.

```jsx

import React from 'react';
import { StaticConfigWrapper, Context } from 'react-static-config-loader';

export class ExampleClass extends React.Component {
  static contextType = Context;
  render() {
    const {someValue} = this.props;
    const config = this.context;
    return <React.Fragment>
      <code>{JSON.stringify(config,null,4)}</code>
      <div>{someValue}</div>
    </React.Fragment>
  }
}

// refer to `later` in the reference section

const App = () => {
  const fn = ()=> Promise.resolve({msg:'go',version:1234,selection:['no','yes'], buttonName:'go go button'})
  return (
    <React.Fragment>
      <StaticConfigWrapper loader={async () => later(2000, fn)}>
        <ExampleClass someValue={'You made it in ExampleClass'}/>
      </StaticConfigWrapper>
    </React.Fragment>
  )
}

export default App
```

Things get a little complicated if you have "Pure" JSX functions. In this case, the
`contextType` is simply not available. You can bypass this by creating a Higher Order Component (HOC)
and pass the value down via the properties or use the built in `ConfigPropExtenderHoc` which extends
the component and copies the `config` into the component as properties.

```jsx
import React from "react";
import { ConfigPropExtenderHoc, StaticConfigWrapper } from "@psenger/react-static-config-loader";

const PureFunction = ({ config, someValue }) => <React.Fragment>
  <code>{JSON.stringify(config, null, 4)}</code>
  <div>{someValue}</div>
</React.Fragment>

const HOC = ({someValue}) => {
  return (
    <ConfigPropExtenderHoc>
      <PureFunction someValue={someValue} />
    </ConfigPropExtenderHoc>
  );
}

// refer to `later` in the reference section

const App = () => {
  const fn = ()=> Promise.resolve({msg:'go',version:1234,selection:['no','yes'], buttonName:'go go button'})
  return (
    <React.Fragment>
      <StaticConfigWrapper loader={async () => later(2000, fn)} loadingMsg={<div>Loading</div>}>
        <HOC someValue={'You made it in ExampleClass'}/>
      </StaticConfigWrapper>
    </React.Fragment>
  )
}

export default App
```

**Reference JavaScript**

```JavaScript
const later = (delay, fnLater) => Promise.resolve()
  .then(()=>{
    let id;
    return new Promise(function(resolve) {
      if (id) { // this is PURELY a safety precaution
        clearTimeout(id);
        id = undefined;
      }
      id = setTimeout(resolve, delay);
    })
      .then(() => {
        // We need to cut down the possibility of a memory leak. It is
        // assumed some one will copy-cut-and paste this code, and do
        // something really bad. :grin:
        clearTimeout(id);
      })
  })
  .then(fnLater)
```

<!--END_SECTION:file:TUTORIAL.md-->

## API

<!-- Generated by documentation.js. Update this documentation by updating the source code. -->

### Context

*   **See**: [https://reactjs.org/docs/context.html][1]

Context

Type: React.Context\<any>

### Provider

*   **See**: [https://reactjs.org/docs/context.html#contextconsumer][2]

Provider

Type: Context.Provider\<any>

### Consumer

*   **See**: [https://reactjs.org/docs/context.html#contextprovider][3]

Consumer

Type: Context.Provider\<any>

### ConfigPropExtenderHoc

**Extends React.Component**

Use this Wrapper or HOC, Higher Order Component, to copy the `config` object found in context
onto the properties of the first level of children it encapsulates. Because `contextType` can
not be added to JSX functions, you will need to wrap or extend the function to inject the
config value. This HOC, simple clones the JSX element, and copies the context's 'config'
values as properties.

#### Parameters

*   `children` **JSX?** Optional JSX Children, keep in mind this only attaches the property
    to all the first level children ( shallow )
*   `propName` **[String][4]** Optionally you can specify a Property to store the
    config on, the default is 'config' (optional, default `'config'`)

#### Examples

```javascript
import React from 'react'
import { ConfigPropExtenderHoc } from 'react-static-config-loader'

const ExampleFunctionalDiv = ({ config, someValue }) => <React.Fragment>
 <code>{JSON.stringify(config, null, 4)}</code>
 <div>{someValue}</div>
 </React.Fragment>

const HOCExampleFunctionalDiv = (props) => {
  return (
    <React.Fragment>
      <ConfigPropExtenderHoc>
        <ExampleFunctionalDiv {...props} />
      </ConfigPropExtenderHoc>
    </React.Fragment>
  );
}

 export default HOCExampleFunctionalDiv
```

Returns **JSX** 

### loaderCall

Callback responsible for fetching the external configuration. Because it is a promise, the
user can add a 'then' or even use async/await to transform the payload.

Type: [Function][5]

Returns **[Promise][6]\<any>** 

### StaticConfigWrapper

StaticConfigWrapper - is everything wrapped up in one JSX tag. I
expect this will satisfy the majority of scenarios. However, for those that
it does not, the [Provider][7], [Consumer][8], and [Context][9] are all broken out. If you
find you really need them, this might not be a good solution for your project.
[redux Action object][10]

#### Parameters

*   `props` **[Object][11]?** props the JSX props.

    *   `props.children` **JSX.Element** All the JSX children, or null. the default value
        is null. (optional, default `null`)
    *   `props.loader` **[loaderCall][12]** Required function that will "load" the static
        configuration returning a promise. It is assumed the function will return a Promise, that can
        resolve a value or a proper rejection.
    *   `props.loadingMsg` **JSX.Element** The optional JSX that will be displayed while the
        loader is running. (optional, default `null`)

#### Examples

```javascript
import React from 'react';
 import { StaticConfigWrapper, Context } from 'react-static-config-loader';
 export class ExampleClass extends React.Component {
  static contextType = Context;
  render() {
    const {someValue} = this.props;
    const config = this.context;
    return <React.Fragment>
      <code>{JSON.stringify(config,null,4)}</code>
      <div>{someValue}</div>
    </React.Fragment>
  }
 }
 const later = async function later(delay, fnLater) {
  return new Promise(function(resolve) {
    setTimeout(resolve, delay);
  }).then(fnLater);
 }
 const App = () => {
  const fn = ()=> Promise.resolve({msg:'go',version:1234,selection:['no','yes'], buttonName:'go go button'})
  return (
    <React.Fragment>
      <StaticConfigWrapper loader={async () => later(2000, fn)}>
        <ExampleClass someValue={'You made it in ExampleClass'}/>
      </StaticConfigWrapper>
    </React.Fragment>
  )
 }
 export default App
```

Returns **JSX.Element** 

[1]: https://reactjs.org/docs/context.html

[2]: https://reactjs.org/docs/context.html#contextconsumer

[3]: https://reactjs.org/docs/context.html#contextprovider

[4]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String

[5]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function

[6]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise

[7]: #Provider

[8]: #Consumer

[9]: #Context

[10]: http://redux.js.org/docs/basics/Actions.html

[11]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object

[12]: #loadercall


<!--START_SECTION:file:CONTRIBUTING.md-->
## Contributing

Thanks for contributing! 😁 Here are some rules that will make your change to
react-static-config-loader fruitful.

### Rules

* Raise a ticket to the feature or bug can be discussed
* Pull requests are welcome, but must be accompanied by a ticket approved by the repo owner
* You are expected to add a unit test or two to cover the proposed changes.
* Please run the tests and make sure tests are all passing before submitting your pull request
* Do as the Romans do and stick with existing whitespace and formatting conventions (i.e., tabs instead of spaces, etc)
  * we have provided the following: `.editorconfig` and `.eslintrc`
  * Don't tamper with or change `.editorconfig` and `.eslintrc`
* Please consider adding an example under examples/ that demonstrates any new functionality


<!--END_SECTION:file:CONTRIBUTING.md-->

## Deployment Steps

These are notes for deploying to NPM. I used `npmrc` to manage my NPM identities
(`npm i npmrc -g` to install ). Then I created a new profile called `public` with
(`npmrc -c public`) and then switch to it with `npmrc public`.

* create a pull request from `dev` to `main`
* check out `main`
* `npm version patch -m "message here" or minor`
* `npm publish --access public`
* Then switch to `dev` branch
* And then merge `main` into `dev` and push `dev` to origin

## License

<!--START_SECTION:file:license-->
MIT License

Copyright (c) 2021 Philip A Senger

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

<!--END_SECTION:file:license-->

MIT © [psenger](https://github.com/psenger)