Full example of using Redux-like State Components with a form validator and loading state component.
import { Children, Component, createElement } from 'react'
const throwError = () => {
throw new Error(
'Missing either `children`, `component`, or `render` prop.',
)
}
class StateSubscriber extends Component {
constructor(props) {
super(props)
const { subscribe } = props
this.unsubscribe = (
subscribe(
this
.setState
.bind(this)
)
)
}
componentWillUnmount() {
this.unsubscribe
&& this.unsubscribe()
}
render() {
const {
children,
component,
render,
...props
} = this.props
const childProps = {
...props,
...this.state,
}
return (
component
? createElement(component, childProps)
: (
render
? render(childProps)
: (
children
? (
typeof children === 'function'
? children(childProps)
: Children.only(children)
)
: throwError()
)
)
)
}
}
const createStateSubscriber = (
renderProps,
) => (
props,
) => (
createElement(
StateSubscriber,
{
...props,
...renderProps,
},
)
)
export default createStateSubscriber
import { Children, Component, createElement } from 'react'
const throwError = () => {
throw new Error(
'Missing either `children`, `component`, or `render` prop.',
)
}
class StateSubscriber extends Component {
constructor(props) {
super(props)
const { subscribe } = props
this.unsubscribe = (
subscribe(
this
.setState
.bind(this)
)
)
}
componentWillUnmount() {
this.unsubscribe
&& this.unsubscribe()
}
render() {
const {
children,
component,
render,
...props
} = this.props
const childProps = {
...props,
...this.state,
}
return (
component
? createElement(component, childProps)
: (
render
? render(childProps)
: (
children
? (
typeof children === 'function'
? children(childProps)
: Children.only(children)
)
: throwError()
)
)
)
}
}
const createStateSubscriber = (
dispatchActions,
) => (
props,
) => (
createElement(
StateSubscriber,
{
...props,
...dispatchActions,
},
)
)
export default createStateSubscriber
import React from 'react'
import FormValidationState from './FormValidationState'
const onSubmit = (
validator,
) => (
event,
) => {
event
.preventDefault()
validator({
fieldNames: [
'search',
],
formElement: (
event
.target
),
})
}
const Form = ({
errorMessage,
}) => (
<FormValidationState>
{({
isValid,
validateFields,
}) => (
<form onSubmit={onSubmit(validateFields)}>
{
isValid
&& (
<div className="error-message">
{errorMessage}
</div>
)
}
<label htmlFor="search">
<input
id="search"
type="text"
/>
</label>
<input type="submit" value="Submit" />
</form>
)}
</FormValidationState>
)
export default Form
import createRenderProps from './utils/createRenderProps'
import createReducer from './utils/createReducer'
import createStateSubscriber from './utils/createStateSubscriber'
export const VALIDATE_FIELDS = 'VALIDATE_FIELDS'
export const actions = {
validateFields: ({
fieldNames,
formElement,
}) => ({
type: VALIDATE_FIELDS,
fieldNames,
formElement,
}),
}
export const initialState = false
export const reducerActions = {
[VALIDATE_FIELDS]: (
prevState,
{
fieldNames,
formElement,
},
) => (
fieldNames
.map(fieldName => (
formElement[fieldName]
.value
))
.every(Boolean)
),
}
export const isValidReducer = (
createReducer(
reducerActions,
initialState,
)
)
const namespacedReducers = {
isValid: isValidReducer,
}
const FormValidationState = (
createStateSubscriber(
createRenderProps({
actions,
namespacedReducers,
})
)
)
export default FormValidationState
import createRenderProps from './utils/createRenderProps'
import createReducer from './utils/createReducer'
import createStateSubscriber from './utils/createStateSubscriber'
export const LOADING = 'LOADING'
export const LOADED = 'LOADED'
export const actions = {
setLoading: () => ({
type: LOADING,
}),
setLoaded: () => ({
type: LOADED,
}),
}
export const initialState = false
export const reducerActions = {
[LOADING]: () => true,
[LOADED]: () => initialState,
}
export const isLoadingReducer = (
createReducer(
reducerActions,
initialState,
)
)
const namespacedReducers = {
isLoading: isLoadingReducer,
}
const LoadingState = (
createStateSubscriber(
createRenderProps({
actions,
namespacedReducers,
})
)
)
export default LoadingState
import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
import Form from './Form'
import Lifecycles from './Lifecycles'
import LoadingState from './LoadingState'
const App = () => (
<div>
<LoadingState>
{({
isLoading,
setLoading,
setLoaded,
}) =>
<Fragment>
<Lifecycles
initialize={() => {
setLoading()
setTimeout(
setLoaded,
2000,
)
}}
/>
{
isLoading
? 'Loading...'
: 'Loaded'
}
</Fragment>
}
</LoadingState>
<Form errorMessage="It's working, but broken???" />
</div>
)
const rootElement = (
document
.getElementById('root')
)
ReactDOM
.render(
<App />,
rootElement,
)