@psenger/react-static-config-loader v1.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.
Table of contents
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.
npm install @psenger/react-static-config-loader --save
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.
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.
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
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)
API
Context
Context
Type: React.Context\
Provider
Provider
Type: Context.Provider\
Consumer
Consumer
Type: Context.Provider\
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 Optionally you can specify a Property to store the config on, the default is 'config' (optional, default'config'
)
Examples
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
Returns Promise\
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, Consumer, and Context are all broken out. If you find you really need them, this might not be a good solution for your project. redux Action object
Parameters
props
Object? props the JSX props.props.children
JSX.Element All the JSX children, or null. the default value is null. (optional, defaultnull
)props.loader
loaderCall 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, defaultnull
)
Examples
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
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
- we have provided the following:
- Please consider adding an example under examples/ that demonstrates any new functionality
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
tomain
- check out
main
npm version patch -m "message here" or minor
npm publish --access public
- Then switch to
dev
branch - And then merge
main
intodev
and pushdev
to origin
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.
MIT © psenger