You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This is an incomplete draft for a feature I think could be
really cool. It can replace higher order components
and context in a way I think is more in the component
spirit of React.
I do not know if this feature is feasible or desirable for
React, especially as it would lead to a bigger API surface.
The proposal is written as if it was documentation to
give a feel for how it would be to use it.
About React middleware
A middleware is applied somewhere in the component tree
and are instantiated just after child components are
instantiated and just before they mount. In this context,
child components means child components at any depth.
Middleware is used just like normal components, but
it works slightly differently. When a middleware element
is used it added to the middleware stack. If it is
already on the middleware stack, it removed from
the stack and pushed to the end, with the most
innermost props.
Simplified example
In addition to the actual classes, the stack also
includes its most recent props. But this is roughly
how it works.
<MiddlewareA>{/* middleware stack for "A": [MiddlewareA] */}<A/><MiddlewareB>{/* middleware stack for "B": [MiddlewareA, MiddlewareB] */}<B><MiddlewareA{/* middleware stack for "C": [MiddlewareB, MiddlewareA] */}<C/></MiddlewareA></B></MiddlewareB></MiddlewareB>
Lifecycle methods
Additions to the existing lifecycle methods
Mounting
new: Middleware.shouldMiddlewareMount
new: Middleware.shouldMiddlewarePropagate
Component#constructor
new: Middleware#constructor
new: Middleware#middlewareWillMount()
Component#componentWillMount()
Component#render()
new: Middleware#interceptRender()
Component#componentDidMount
Unmounting
new: Middleware#middlewareWillUnmount
static shouldMiddlewareMount(ReactComponent)
Determine if the current middleware should apply for
a component. If the method isn't implemented, the
middleware will always be applied.
If a middleware is on the middleware stack, this method
is called every time a component is constructed.
Example
classTransformInlineStylesextendsReact.Middleware{/** * Only mount middleware when you set * transformInlineStyles to a truthy * value. Children of the given * component can still enable * the middleware */staticshouldMiddlewareMount(Component){returnComponent.transformInlineStyles}// ...}constA=props=>(// ...)constB=props=>(// ...)B.transformInlineStyles=trueconstApp=()=>(<TransformInlineStyles>{/* Not applied to "A" */}<A>{/* Applied to "B" */}<B/></A></TransformInlineStyles>)
static shouldMiddlewarePropagate(ReactComponent)
Determine whether the middleware should remain on
the middleware stack or be excluded for the subtree
below the given component. If not specified it returns
false, in other words: The default behavior for
middleware is to propagate.
This is useful if you want to limit middleware from
affecting deeply nested children. It is also useful
for only giving middleware access to its immediate
children.
Example
importReactfrom'react'classProvideThemeextendsReact.Middleware{staticStopPropagation=props=>props.childrenstaticshouldMiddlewarePropagate(Component){returnComponent!==this.StopPropagation}// ...}constApp=()=>(<ProvideTheme>{/* middleware stack for "A": [ProvideTheme] */}<A><ProvideTheme.StopPropagation>{/* middleware stack for "B": [] */}<B/></ProvideTheme.StopPropagation>{/* middleware stack for "C": [ProvideTheme] */}<C/></A></ProvideTheme>)
middlewareWillMount(reactInstance)
Called before the child component calls componentWillMount.
This is a good place to initialize state for the middleware
instance.
MiddlewareWillUnmount(reactInstance)
Called before the child component calls componentWillUnmount.
Example
This is a naïve example of how it could be used to trigger
automatic updates with Mobx.
classObserverextendsReact.Middleware{middlewareWillMount(reactInstance){this.dispose=autorun(()=>{// Let Mobx track the observables// used in the render method.reactInstance.render()// Force update the component instance// after Mobx has stopped tracking the// autorun function.//// .. yes, I know it's hacky.setTimeout(()=>{reactInstance.forceUpdate()})})}middlewareWillUnmount(reactInstance){// Stop listening for changes from Mobxthis.dispose()}}
interceptRender(children)
interceptRender is called with the result from the render
function of the component. The resulting value is what is
used to render the DOM.
Example
This is an example of a middleware that transforms object
classes into a string. The result works similarly to how
ng-class works in AngularJS.
classObjectClassNamesextendsReact.Middleware{/** * This is a life cycle method. * * Intercept the render method and recursively * loop through all children, performing * this.transformProps() on their props. */interceptRender(children){returnReact.Children.map(children,child=>{if(!React.isValidElement(child)){returnchild}return{
...child,props: this.transformProps(child.props),children: this.interceptRender(child.children)}})}/** * If a child has an object className, call * this.transformClassname() on it. */transformProps(props){if(!props||!props.className||typeofprops.className!=='object'){returnprops}return{
...props,className: this.transformClassname(props.className)}}/** * Concatenate the truthy keys of the className * object into a string. */transformClassname(className){constresult=[]for(constkeyofObject.keys(className)){if(className[key]){result.push(key)}}returnresult.join(' ')}}constWidget=(props)=>(<divclassName={{'Widget': true,'Widget--active': props.active}}>
Some widget
</div>)constApp=()=>(<ObjectClassNames><Widgetactive={true}/></ObjectClassNames>)
Why middleware?
React middleware can replace two problematic patterns used with React.
Context
The h2 on context in the React docs says "Why Not To Use
Context". Context is however a very useful feature. And
people have been and will continue to use and abuse it
in the forseeable future. React Router has started
abusing context in its most recent version, which shows
that there is clearly a need here.
With middleware, as I propose it, you would be able to
inject props into an arbitrary subtree of your app. This
has performance implications, but would be an ideal
scenario for libraries such as React Router, as the
relevant props (or as it is now, context) rarely
changes. With middleware shouldComponentUpdate
will still function like you would expect.
Higher order components
A primal rule of programming is DRY. When using Mobx
with React, you must use the observer decorator on
all reactive classes. This isn't a really big deal,
but not having to include that would reduce the size
of every single observer component by two lines and
most importantly, I wouldn't forget it.
When creating a higher order component static properties
are no longer available. The package hoist-non-react-static
is designed so that you should be able to access static
properties of higher order components transparently.
If a static property is initialized in the lifecycle
methods of a component, it will however not be proxied.
Creating higher order components is also a messy affair.
With middleware you could achive the same thing in a React
way. To replace connect from react-redux you could set
shouldMiddlewarePropagate to return false, and it would
affect only one component.
Alternatively you could use static properties for
mapStateToProps and mapDispatchToProps.
The text was updated successfully, but these errors were encountered:
I appreciate the detailed writeup but overall I think this proposal is counter to how we think about React, and I don’t really see this happening.
We are fixing context so at least that part of the motivation is solved.
While using systems like MobX that wrap all the data structures is possible in React, we don't necessarily want to encourage this style of programming, and I don't think making it even more automatic is something we'd want to do.
I think the biggest drawback of this proposal is that it's both implicit and very powerful. We try not to combine those. The only implicit API we have is context, and even that only affects data and not behavior. It's also opt-in and passes the grep test. The middleware API can't pass it because it affects all components indirectly. This essentially changes the contract between components, saying "here are your props, unless there's middleware on the stack, in which case who knows what you'll really get as props". That defies any potential optimizations React could make, including compilation techniques we're currently exploring.
This is an incomplete draft for a feature I think could be
really cool. It can replace higher order components
and context in a way I think is more in the component
spirit of React.
I do not know if this feature is feasible or desirable for
React, especially as it would lead to a bigger API surface.
The proposal is written as if it was documentation to
give a feel for how it would be to use it.
About React middleware
A middleware is applied somewhere in the component tree
and are instantiated just after child components are
instantiated and just before they mount. In this context,
child components means child components at any depth.
Middleware is used just like normal components, but
it works slightly differently. When a middleware element
is used it added to the middleware stack. If it is
already on the middleware stack, it removed from
the stack and pushed to the end, with the most
innermost props.
Simplified example
In addition to the actual classes, the stack also
includes its most recent props. But this is roughly
how it works.
Lifecycle methods
Additions to the existing lifecycle methods
Mounting
Unmounting
static shouldMiddlewareMount(ReactComponent)
Determine if the current middleware should apply for
a component. If the method isn't implemented, the
middleware will always be applied.
If a middleware is on the middleware stack, this method
is called every time a component is constructed.
Example
static shouldMiddlewarePropagate(ReactComponent)
Determine whether the middleware should remain on
the middleware stack or be excluded for the subtree
below the given component. If not specified it returns
false, in other words: The default behavior for
middleware is to propagate.
This is useful if you want to limit middleware from
affecting deeply nested children. It is also useful
for only giving middleware access to its immediate
children.
Example
middlewareWillMount(reactInstance)
Called before the child component calls componentWillMount.
This is a good place to initialize state for the middleware
instance.
MiddlewareWillUnmount(reactInstance)
Called before the child component calls componentWillUnmount.
Example
This is a naïve example of how it could be used to trigger
automatic updates with Mobx.
interceptRender(children)
interceptRender is called with the result from the render
function of the component. The resulting value is what is
used to render the DOM.
Example
This is an example of a middleware that transforms object
classes into a string. The result works similarly to how
ng-class works in AngularJS.
Why middleware?
React middleware can replace two problematic patterns used with React.
Context
The h2 on context in the React docs says "Why Not To Use
Context". Context is however a very useful feature. And
people have been and will continue to use and abuse it
in the forseeable future. React Router has started
abusing context in its most recent version, which shows
that there is clearly a need here.
With middleware, as I propose it, you would be able to
inject props into an arbitrary subtree of your app. This
has performance implications, but would be an ideal
scenario for libraries such as React Router, as the
relevant props (or as it is now, context) rarely
changes. With middleware shouldComponentUpdate
will still function like you would expect.
Higher order components
A primal rule of programming is DRY. When using Mobx
with React, you must use the observer decorator on
all reactive classes. This isn't a really big deal,
but not having to include that would reduce the size
of every single observer component by two lines and
most importantly, I wouldn't forget it.
When creating a higher order component static properties
are no longer available. The package hoist-non-react-static
is designed so that you should be able to access static
properties of higher order components transparently.
If a static property is initialized in the lifecycle
methods of a component, it will however not be proxied.
Creating higher order components is also a messy affair.
With middleware you could achive the same thing in a React
way. To replace connect from react-redux you could set
shouldMiddlewarePropagate to return false, and it would
affect only one component.
Alternatively you could use static properties for
mapStateToProps and mapDispatchToProps.
The text was updated successfully, but these errors were encountered: