mobx-react-lite
Advanced tools
Comparing version 0.2.0 to 0.3.0
@@ -8,6 +8,6 @@ (function (global, factory) { | ||
if (!react.useState) { | ||
throw new Error("mobx-react requires React 16.7 to be available"); | ||
throw new Error("mobx-react-lite requires React 16.7 to be available"); | ||
} | ||
if (!mobx.spy) { | ||
throw new Error("mobx-react requires mobx to be available"); | ||
throw new Error("mobx-react-lite requires mobx to be available"); | ||
} | ||
@@ -19,49 +19,102 @@ | ||
function useComputed(initialValue, inputs = []) { | ||
const computed = react.useMemo(() => mobx.computed(initialValue), inputs); | ||
function useComputed(func, inputs = []) { | ||
const computed = react.useMemo(() => mobx.computed(func), inputs); | ||
return computed.get(); | ||
} | ||
let isUsingStaticRendering = false; | ||
/** | ||
* Adds an observable effect (reaction, autorun, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting. | ||
* Returns the generated disposer for early disposal. | ||
* | ||
* @export | ||
* @template D | ||
* @param {() => D} disposerGenerator A function that returns the disposer of the wanted effect. | ||
* @param {ReadonlyArray<any>} [inputs=[]] If you want the effect to be automatically re-created when some variable(s) are changed then pass them in this array. | ||
* @returns {D} | ||
*/ | ||
function useObservableEffect(disposerGenerator, inputs = []) { | ||
const disposerRef = react.useRef(undefined); | ||
react.useMemo(() => { | ||
disposerRef.current = disposerGenerator(); | ||
}, inputs); | ||
react.useEffect(() => () => { | ||
if (disposerRef.current) { | ||
disposerRef.current(); | ||
} | ||
}, inputs); | ||
return disposerRef.current; | ||
} | ||
let globalIsUsingStaticRendering = false; | ||
function useStaticRendering(enable) { | ||
isUsingStaticRendering = enable; | ||
globalIsUsingStaticRendering = enable; | ||
} | ||
function observer(baseComponent) { | ||
if (isUsingStaticRendering) { | ||
return baseComponent; | ||
} | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
return react.memo(props => { | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
// create a Reaction once, and memoize it | ||
const reaction = react.useMemo(() => | ||
// If the Reaction detects a change in dependency, | ||
// force a new render | ||
new mobx.Reaction(`observer(${baseComponent.displayName || baseComponent.name})`, forceUpdate), []); | ||
// clean up the reaction if this component is unMount | ||
useUnmount(() => reaction.dispose()); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.track(() => { | ||
rendering = baseComponent(props); | ||
}); | ||
return rendering; | ||
}); | ||
function isUsingStaticRendering() { | ||
return globalIsUsingStaticRendering; | ||
} | ||
const EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
react.useEffect(() => fn, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
const [tick, setTick] = react.useState(1); | ||
return () => { | ||
const update = react.useCallback(() => { | ||
setTick(tick + 1); | ||
}; | ||
}, []); | ||
return update; | ||
} | ||
function useUnmount(fn) { | ||
react.useEffect(() => fn, []); | ||
function useObserver(fn, baseComponentName = "observed") { | ||
if (isUsingStaticRendering()) { | ||
return fn(); | ||
} | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
const reaction = react.useRef(new mobx.Reaction(`observer(${baseComponentName})`, () => { | ||
forceUpdate(); | ||
})); | ||
useUnmount(() => { | ||
reaction.current.dispose(); | ||
}); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.current.track(() => { | ||
rendering = fn(); | ||
}); | ||
return rendering; | ||
} | ||
const Observer = observer(({ children, render }) => { | ||
// n.b. base case is not used for actual typings or exported in the typing files | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
if (isUsingStaticRendering()) { | ||
return baseComponent; | ||
} | ||
const realOptions = Object.assign({ forwardRef: false }, options); | ||
const baseComponentName = baseComponent.displayName || baseComponent.name; | ||
const wrappedComponent = (props, ref) => { | ||
return useObserver(() => baseComponent(props, ref), baseComponentName); | ||
}; | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
let memoComponent; | ||
if (realOptions.forwardRef) { | ||
// we have to use forwardRef here because: | ||
// 1. it cannot go before memo, only after it | ||
// 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it | ||
// since it wouldn't be a callable function anymore | ||
memoComponent = react.memo(react.forwardRef(wrappedComponent)); | ||
} | ||
else { | ||
memoComponent = react.memo(wrappedComponent); | ||
} | ||
memoComponent.displayName = baseComponentName; | ||
return memoComponent; | ||
} | ||
function ObserverComponent({ children, render }) { | ||
const component = children || render; | ||
@@ -72,8 +125,9 @@ if (typeof component === "undefined") { | ||
return component(); | ||
}); | ||
Observer.displayName = "Observer"; | ||
Observer.propTypes = { | ||
} | ||
ObserverComponent.propTypes = { | ||
children: ObserverPropsCheck, | ||
render: ObserverPropsCheck | ||
}; | ||
ObserverComponent.displayName = "Observer"; | ||
const Observer = observer(ObserverComponent); | ||
function ObserverPropsCheck(props, key, componentName, location, propFullName) { | ||
@@ -101,4 +155,7 @@ const extraKey = key === "children" ? "render" : "children"; | ||
exports.useComputed = useComputed; | ||
exports.useObservableEffect = useObservableEffect; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.observer = observer; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.useObserver = useObserver; | ||
exports.Observer = Observer; | ||
@@ -105,0 +162,0 @@ |
import { spy, observable, Reaction, computed } from 'mobx'; | ||
import { useState, useRef, useMemo, memo, useEffect } from 'react'; | ||
import { useState, useRef, useMemo, useEffect, useCallback, forwardRef, memo } from 'react'; | ||
if (!useState) { | ||
throw new Error("mobx-react requires React 16.7 to be available"); | ||
throw new Error("mobx-react-lite requires React 16.7 to be available"); | ||
} | ||
if (!spy) { | ||
throw new Error("mobx-react requires mobx to be available"); | ||
throw new Error("mobx-react-lite requires mobx to be available"); | ||
} | ||
@@ -15,49 +15,102 @@ | ||
function useComputed(initialValue, inputs = []) { | ||
const computed$$1 = useMemo(() => computed(initialValue), inputs); | ||
function useComputed(func, inputs = []) { | ||
const computed$$1 = useMemo(() => computed(func), inputs); | ||
return computed$$1.get(); | ||
} | ||
let isUsingStaticRendering = false; | ||
/** | ||
* Adds an observable effect (reaction, autorun, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting. | ||
* Returns the generated disposer for early disposal. | ||
* | ||
* @export | ||
* @template D | ||
* @param {() => D} disposerGenerator A function that returns the disposer of the wanted effect. | ||
* @param {ReadonlyArray<any>} [inputs=[]] If you want the effect to be automatically re-created when some variable(s) are changed then pass them in this array. | ||
* @returns {D} | ||
*/ | ||
function useObservableEffect(disposerGenerator, inputs = []) { | ||
const disposerRef = useRef(undefined); | ||
useMemo(() => { | ||
disposerRef.current = disposerGenerator(); | ||
}, inputs); | ||
useEffect(() => () => { | ||
if (disposerRef.current) { | ||
disposerRef.current(); | ||
} | ||
}, inputs); | ||
return disposerRef.current; | ||
} | ||
let globalIsUsingStaticRendering = false; | ||
function useStaticRendering(enable) { | ||
isUsingStaticRendering = enable; | ||
globalIsUsingStaticRendering = enable; | ||
} | ||
function observer(baseComponent) { | ||
if (isUsingStaticRendering) { | ||
return baseComponent; | ||
} | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
return memo(props => { | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
// create a Reaction once, and memoize it | ||
const reaction = useMemo(() => | ||
// If the Reaction detects a change in dependency, | ||
// force a new render | ||
new Reaction(`observer(${baseComponent.displayName || baseComponent.name})`, forceUpdate), []); | ||
// clean up the reaction if this component is unMount | ||
useUnmount(() => reaction.dispose()); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.track(() => { | ||
rendering = baseComponent(props); | ||
}); | ||
return rendering; | ||
}); | ||
function isUsingStaticRendering() { | ||
return globalIsUsingStaticRendering; | ||
} | ||
const EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
useEffect(() => fn, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
const [tick, setTick] = useState(1); | ||
return () => { | ||
const update = useCallback(() => { | ||
setTick(tick + 1); | ||
}; | ||
}, []); | ||
return update; | ||
} | ||
function useUnmount(fn) { | ||
useEffect(() => fn, []); | ||
function useObserver(fn, baseComponentName = "observed") { | ||
if (isUsingStaticRendering()) { | ||
return fn(); | ||
} | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
const reaction = useRef(new Reaction(`observer(${baseComponentName})`, () => { | ||
forceUpdate(); | ||
})); | ||
useUnmount(() => { | ||
reaction.current.dispose(); | ||
}); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.current.track(() => { | ||
rendering = fn(); | ||
}); | ||
return rendering; | ||
} | ||
const Observer = observer(({ children, render }) => { | ||
// n.b. base case is not used for actual typings or exported in the typing files | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
if (isUsingStaticRendering()) { | ||
return baseComponent; | ||
} | ||
const realOptions = Object.assign({ forwardRef: false }, options); | ||
const baseComponentName = baseComponent.displayName || baseComponent.name; | ||
const wrappedComponent = (props, ref) => { | ||
return useObserver(() => baseComponent(props, ref), baseComponentName); | ||
}; | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
let memoComponent; | ||
if (realOptions.forwardRef) { | ||
// we have to use forwardRef here because: | ||
// 1. it cannot go before memo, only after it | ||
// 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it | ||
// since it wouldn't be a callable function anymore | ||
memoComponent = memo(forwardRef(wrappedComponent)); | ||
} | ||
else { | ||
memoComponent = memo(wrappedComponent); | ||
} | ||
memoComponent.displayName = baseComponentName; | ||
return memoComponent; | ||
} | ||
function ObserverComponent({ children, render }) { | ||
const component = children || render; | ||
@@ -68,8 +121,9 @@ if (typeof component === "undefined") { | ||
return component(); | ||
}); | ||
Observer.displayName = "Observer"; | ||
Observer.propTypes = { | ||
} | ||
ObserverComponent.propTypes = { | ||
children: ObserverPropsCheck, | ||
render: ObserverPropsCheck | ||
}; | ||
ObserverComponent.displayName = "Observer"; | ||
const Observer = observer(ObserverComponent); | ||
function ObserverPropsCheck(props, key, componentName, location, propFullName) { | ||
@@ -95,2 +149,2 @@ const extraKey = key === "children" ? "render" : "children"; | ||
export { useObservable, useComputed, observer, useStaticRendering, Observer }; | ||
export { useObservable, useComputed, useObservableEffect, isUsingStaticRendering, useStaticRendering, observer, useObserver, Observer }; |
@@ -8,6 +8,6 @@ (function (global, factory) { | ||
if (!react.useState) { | ||
throw new Error("mobx-react requires React 16.7 to be available"); | ||
throw new Error("mobx-react-lite requires React 16.7 to be available"); | ||
} | ||
if (!mobx.spy) { | ||
throw new Error("mobx-react requires mobx to be available"); | ||
throw new Error("mobx-react-lite requires mobx to be available"); | ||
} | ||
@@ -19,49 +19,102 @@ | ||
function useComputed(initialValue, inputs = []) { | ||
const computed = react.useMemo(() => mobx.computed(initialValue), inputs); | ||
function useComputed(func, inputs = []) { | ||
const computed = react.useMemo(() => mobx.computed(func), inputs); | ||
return computed.get(); | ||
} | ||
let isUsingStaticRendering = false; | ||
/** | ||
* Adds an observable effect (reaction, autorun, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting. | ||
* Returns the generated disposer for early disposal. | ||
* | ||
* @export | ||
* @template D | ||
* @param {() => D} disposerGenerator A function that returns the disposer of the wanted effect. | ||
* @param {ReadonlyArray<any>} [inputs=[]] If you want the effect to be automatically re-created when some variable(s) are changed then pass them in this array. | ||
* @returns {D} | ||
*/ | ||
function useObservableEffect(disposerGenerator, inputs = []) { | ||
const disposerRef = react.useRef(undefined); | ||
react.useMemo(() => { | ||
disposerRef.current = disposerGenerator(); | ||
}, inputs); | ||
react.useEffect(() => () => { | ||
if (disposerRef.current) { | ||
disposerRef.current(); | ||
} | ||
}, inputs); | ||
return disposerRef.current; | ||
} | ||
let globalIsUsingStaticRendering = false; | ||
function useStaticRendering(enable) { | ||
isUsingStaticRendering = enable; | ||
globalIsUsingStaticRendering = enable; | ||
} | ||
function observer(baseComponent) { | ||
if (isUsingStaticRendering) { | ||
return baseComponent; | ||
} | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
return react.memo(props => { | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
// create a Reaction once, and memoize it | ||
const reaction = react.useMemo(() => | ||
// If the Reaction detects a change in dependency, | ||
// force a new render | ||
new mobx.Reaction(`observer(${baseComponent.displayName || baseComponent.name})`, forceUpdate), []); | ||
// clean up the reaction if this component is unMount | ||
useUnmount(() => reaction.dispose()); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.track(() => { | ||
rendering = baseComponent(props); | ||
}); | ||
return rendering; | ||
}); | ||
function isUsingStaticRendering() { | ||
return globalIsUsingStaticRendering; | ||
} | ||
const EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
react.useEffect(() => fn, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
const [tick, setTick] = react.useState(1); | ||
return () => { | ||
const update = react.useCallback(() => { | ||
setTick(tick + 1); | ||
}; | ||
}, []); | ||
return update; | ||
} | ||
function useUnmount(fn) { | ||
react.useEffect(() => fn, []); | ||
function useObserver(fn, baseComponentName = "observed") { | ||
if (isUsingStaticRendering()) { | ||
return fn(); | ||
} | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
const reaction = react.useRef(new mobx.Reaction(`observer(${baseComponentName})`, () => { | ||
forceUpdate(); | ||
})); | ||
useUnmount(() => { | ||
reaction.current.dispose(); | ||
}); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.current.track(() => { | ||
rendering = fn(); | ||
}); | ||
return rendering; | ||
} | ||
const Observer = observer(({ children, render }) => { | ||
// n.b. base case is not used for actual typings or exported in the typing files | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
if (isUsingStaticRendering()) { | ||
return baseComponent; | ||
} | ||
const realOptions = Object.assign({ forwardRef: false }, options); | ||
const baseComponentName = baseComponent.displayName || baseComponent.name; | ||
const wrappedComponent = (props, ref) => { | ||
return useObserver(() => baseComponent(props, ref), baseComponentName); | ||
}; | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
let memoComponent; | ||
if (realOptions.forwardRef) { | ||
// we have to use forwardRef here because: | ||
// 1. it cannot go before memo, only after it | ||
// 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it | ||
// since it wouldn't be a callable function anymore | ||
memoComponent = react.memo(react.forwardRef(wrappedComponent)); | ||
} | ||
else { | ||
memoComponent = react.memo(wrappedComponent); | ||
} | ||
memoComponent.displayName = baseComponentName; | ||
return memoComponent; | ||
} | ||
function ObserverComponent({ children, render }) { | ||
const component = children || render; | ||
@@ -72,8 +125,9 @@ if (typeof component === "undefined") { | ||
return component(); | ||
}); | ||
Observer.displayName = "Observer"; | ||
Observer.propTypes = { | ||
} | ||
ObserverComponent.propTypes = { | ||
children: ObserverPropsCheck, | ||
render: ObserverPropsCheck | ||
}; | ||
ObserverComponent.displayName = "Observer"; | ||
const Observer = observer(ObserverComponent); | ||
function ObserverPropsCheck(props, key, componentName, location, propFullName) { | ||
@@ -101,4 +155,7 @@ const extraKey = key === "children" ? "render" : "children"; | ||
exports.useComputed = useComputed; | ||
exports.useObservableEffect = useObservableEffect; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.observer = observer; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.useObserver = useObserver; | ||
exports.Observer = Observer; | ||
@@ -105,0 +162,0 @@ |
@@ -1,1 +0,1 @@ | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("mobx"),require("react")):"function"==typeof define&&define.amd?define(["exports","mobx","react"],r):r(e.mobxReact={},e.mobx,e.React)}(this,function(e,r,t){"use strict";if(!t.useState)throw new Error("mobx-react requires React 16.7 to be available");if(!r.spy)throw new Error("mobx-react requires mobx to be available");let n=!1;function o(e){return n?e:t.memo(n=>{const o=function(){const[e,r]=t.useState(1);return()=>{r(e+1)}}(),i=t.useMemo(()=>new r.Reaction(`observer(${e.displayName||e.name})`,o),[]);let u;return function(e){t.useEffect(()=>e,[])}(()=>i.dispose()),i.track(()=>{u=e(n)}),u})}const i=o(({children:e,render:r})=>{const t=e||r;return void 0===t?null:t()});function u(e,r,t,n,o){const i="children"===r?"render":"children",u="function"==typeof e[r],c="function"==typeof e[i];return u&&c?new Error("MobX Observer: Do not use children and render in the same time in`"+t):u||c?null:new Error("Invalid prop `"+o+"` of type `"+typeof e[r]+"` supplied to `"+t+"`, expected `function`.")}i.displayName="Observer",i.propTypes={children:u,render:u},e.useObservable=function(e){return t.useRef(r.observable(e)).current},e.useComputed=function(e,n=[]){return t.useMemo(()=>r.computed(e),n).get()},e.observer=o,e.useStaticRendering=function(e){n=e},e.Observer=i,Object.defineProperty(e,"__esModule",{value:!0})}); | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("mobx"),require("react")):"function"==typeof define&&define.amd?define(["exports","mobx","react"],r):r(e.mobxReact={},e.mobx,e.React)}(this,function(e,r,t){"use strict";if(!t.useState)throw new Error("mobx-react-lite requires React 16.7 to be available");if(!r.spy)throw new Error("mobx-react-lite requires mobx to be available");let n=!1;function o(){return n}const u=[];function i(e,n="observed"){if(o())return e();const i=function(){const[e,r]=t.useState(1);return t.useCallback(()=>{r(e+1)},[])}(),c=t.useRef(new r.Reaction(`observer(${n})`,()=>{i()}));let s;return function(e){t.useEffect(()=>e,u)}(()=>{c.current.dispose()}),c.current.track(()=>{s=e()}),s}function c(e,r){if(o())return e;const n=Object.assign({forwardRef:!1},r),u=e.displayName||e.name,c=(r,t)=>i(()=>e(r,t),u);let s;return(s=n.forwardRef?t.memo(t.forwardRef(c)):t.memo(c)).displayName=u,s}function s({children:e,render:r}){const t=e||r;return void 0===t?null:t()}s.propTypes={children:a,render:a},s.displayName="Observer";const f=c(s);function a(e,r,t,n,o){const u="children"===r?"render":"children",i="function"==typeof e[r],c="function"==typeof e[u];return i&&c?new Error("MobX Observer: Do not use children and render in the same time in`"+t):i||c?null:new Error("Invalid prop `"+o+"` of type `"+typeof e[r]+"` supplied to `"+t+"`, expected `function`.")}e.useObservable=function(e){return t.useRef(r.observable(e)).current},e.useComputed=function(e,n=[]){return t.useMemo(()=>r.computed(e),n).get()},e.useObservableEffect=function(e,r=[]){const n=t.useRef(void 0);return t.useMemo(()=>{n.current=e()},r),t.useEffect(()=>()=>{n.current&&n.current()},r),n.current},e.isUsingStaticRendering=o,e.useStaticRendering=function(e){n=e},e.observer=c,e.useObserver=i,e.Observer=f,Object.defineProperty(e,"__esModule",{value:!0})}); |
import { spy, observable, Reaction, computed } from 'mobx'; | ||
import { useState, useRef, useMemo, memo, useEffect } from 'react'; | ||
import { useState, useRef, useMemo, useEffect, useCallback, forwardRef, memo } from 'react'; | ||
if (!useState) { | ||
throw new Error("mobx-react requires React 16.7 to be available"); | ||
throw new Error("mobx-react-lite requires React 16.7 to be available"); | ||
} | ||
if (!spy) { | ||
throw new Error("mobx-react requires mobx to be available"); | ||
throw new Error("mobx-react-lite requires mobx to be available"); | ||
} | ||
@@ -15,49 +15,102 @@ | ||
function useComputed(initialValue, inputs = []) { | ||
const computed$$1 = useMemo(() => computed(initialValue), inputs); | ||
function useComputed(func, inputs = []) { | ||
const computed$$1 = useMemo(() => computed(func), inputs); | ||
return computed$$1.get(); | ||
} | ||
let isUsingStaticRendering = false; | ||
/** | ||
* Adds an observable effect (reaction, autorun, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting. | ||
* Returns the generated disposer for early disposal. | ||
* | ||
* @export | ||
* @template D | ||
* @param {() => D} disposerGenerator A function that returns the disposer of the wanted effect. | ||
* @param {ReadonlyArray<any>} [inputs=[]] If you want the effect to be automatically re-created when some variable(s) are changed then pass them in this array. | ||
* @returns {D} | ||
*/ | ||
function useObservableEffect(disposerGenerator, inputs = []) { | ||
const disposerRef = useRef(undefined); | ||
useMemo(() => { | ||
disposerRef.current = disposerGenerator(); | ||
}, inputs); | ||
useEffect(() => () => { | ||
if (disposerRef.current) { | ||
disposerRef.current(); | ||
} | ||
}, inputs); | ||
return disposerRef.current; | ||
} | ||
let globalIsUsingStaticRendering = false; | ||
function useStaticRendering(enable) { | ||
isUsingStaticRendering = enable; | ||
globalIsUsingStaticRendering = enable; | ||
} | ||
function observer(baseComponent) { | ||
if (isUsingStaticRendering) { | ||
return baseComponent; | ||
} | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
return memo(props => { | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
// create a Reaction once, and memoize it | ||
const reaction = useMemo(() => | ||
// If the Reaction detects a change in dependency, | ||
// force a new render | ||
new Reaction(`observer(${baseComponent.displayName || baseComponent.name})`, forceUpdate), []); | ||
// clean up the reaction if this component is unMount | ||
useUnmount(() => reaction.dispose()); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.track(() => { | ||
rendering = baseComponent(props); | ||
}); | ||
return rendering; | ||
}); | ||
function isUsingStaticRendering() { | ||
return globalIsUsingStaticRendering; | ||
} | ||
const EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
useEffect(() => fn, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
const [tick, setTick] = useState(1); | ||
return () => { | ||
const update = useCallback(() => { | ||
setTick(tick + 1); | ||
}; | ||
}, []); | ||
return update; | ||
} | ||
function useUnmount(fn) { | ||
useEffect(() => fn, []); | ||
function useObserver(fn, baseComponentName = "observed") { | ||
if (isUsingStaticRendering()) { | ||
return fn(); | ||
} | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
const reaction = useRef(new Reaction(`observer(${baseComponentName})`, () => { | ||
forceUpdate(); | ||
})); | ||
useUnmount(() => { | ||
reaction.current.dispose(); | ||
}); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.current.track(() => { | ||
rendering = fn(); | ||
}); | ||
return rendering; | ||
} | ||
const Observer = observer(({ children, render }) => { | ||
// n.b. base case is not used for actual typings or exported in the typing files | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
if (isUsingStaticRendering()) { | ||
return baseComponent; | ||
} | ||
const realOptions = Object.assign({ forwardRef: false }, options); | ||
const baseComponentName = baseComponent.displayName || baseComponent.name; | ||
const wrappedComponent = (props, ref) => { | ||
return useObserver(() => baseComponent(props, ref), baseComponentName); | ||
}; | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
let memoComponent; | ||
if (realOptions.forwardRef) { | ||
// we have to use forwardRef here because: | ||
// 1. it cannot go before memo, only after it | ||
// 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it | ||
// since it wouldn't be a callable function anymore | ||
memoComponent = memo(forwardRef(wrappedComponent)); | ||
} | ||
else { | ||
memoComponent = memo(wrappedComponent); | ||
} | ||
memoComponent.displayName = baseComponentName; | ||
return memoComponent; | ||
} | ||
function ObserverComponent({ children, render }) { | ||
const component = children || render; | ||
@@ -68,8 +121,9 @@ if (typeof component === "undefined") { | ||
return component(); | ||
}); | ||
Observer.displayName = "Observer"; | ||
Observer.propTypes = { | ||
} | ||
ObserverComponent.propTypes = { | ||
children: ObserverPropsCheck, | ||
render: ObserverPropsCheck | ||
}; | ||
ObserverComponent.displayName = "Observer"; | ||
const Observer = observer(ObserverComponent); | ||
function ObserverPropsCheck(props, key, componentName, location, propFullName) { | ||
@@ -95,2 +149,2 @@ const extraKey = key === "children" ? "render" : "children"; | ||
export { useObservable, useComputed, observer, useStaticRendering, Observer }; | ||
export { useObservable, useComputed, useObservableEffect, isUsingStaticRendering, useStaticRendering, observer, useObserver, Observer }; |
@@ -9,6 +9,6 @@ 'use strict'; | ||
if (!react.useState) { | ||
throw new Error("mobx-react requires React 16.7 to be available"); | ||
throw new Error("mobx-react-lite requires React 16.7 to be available"); | ||
} | ||
if (!mobx.spy) { | ||
throw new Error("mobx-react requires mobx to be available"); | ||
throw new Error("mobx-react-lite requires mobx to be available"); | ||
} | ||
@@ -20,49 +20,102 @@ | ||
function useComputed(initialValue, inputs = []) { | ||
const computed = react.useMemo(() => mobx.computed(initialValue), inputs); | ||
function useComputed(func, inputs = []) { | ||
const computed = react.useMemo(() => mobx.computed(func), inputs); | ||
return computed.get(); | ||
} | ||
let isUsingStaticRendering = false; | ||
/** | ||
* Adds an observable effect (reaction, autorun, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting. | ||
* Returns the generated disposer for early disposal. | ||
* | ||
* @export | ||
* @template D | ||
* @param {() => D} disposerGenerator A function that returns the disposer of the wanted effect. | ||
* @param {ReadonlyArray<any>} [inputs=[]] If you want the effect to be automatically re-created when some variable(s) are changed then pass them in this array. | ||
* @returns {D} | ||
*/ | ||
function useObservableEffect(disposerGenerator, inputs = []) { | ||
const disposerRef = react.useRef(undefined); | ||
react.useMemo(() => { | ||
disposerRef.current = disposerGenerator(); | ||
}, inputs); | ||
react.useEffect(() => () => { | ||
if (disposerRef.current) { | ||
disposerRef.current(); | ||
} | ||
}, inputs); | ||
return disposerRef.current; | ||
} | ||
let globalIsUsingStaticRendering = false; | ||
function useStaticRendering(enable) { | ||
isUsingStaticRendering = enable; | ||
globalIsUsingStaticRendering = enable; | ||
} | ||
function observer(baseComponent) { | ||
if (isUsingStaticRendering) { | ||
return baseComponent; | ||
} | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
return react.memo(props => { | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
// create a Reaction once, and memoize it | ||
const reaction = react.useMemo(() => | ||
// If the Reaction detects a change in dependency, | ||
// force a new render | ||
new mobx.Reaction(`observer(${baseComponent.displayName || baseComponent.name})`, forceUpdate), []); | ||
// clean up the reaction if this component is unMount | ||
useUnmount(() => reaction.dispose()); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.track(() => { | ||
rendering = baseComponent(props); | ||
}); | ||
return rendering; | ||
}); | ||
function isUsingStaticRendering() { | ||
return globalIsUsingStaticRendering; | ||
} | ||
const EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
react.useEffect(() => fn, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
const [tick, setTick] = react.useState(1); | ||
return () => { | ||
const update = react.useCallback(() => { | ||
setTick(tick + 1); | ||
}; | ||
}, []); | ||
return update; | ||
} | ||
function useUnmount(fn) { | ||
react.useEffect(() => fn, []); | ||
function useObserver(fn, baseComponentName = "observed") { | ||
if (isUsingStaticRendering()) { | ||
return fn(); | ||
} | ||
// forceUpdate 2.0 | ||
const forceUpdate = useForceUpdate(); | ||
const reaction = react.useRef(new mobx.Reaction(`observer(${baseComponentName})`, () => { | ||
forceUpdate(); | ||
})); | ||
useUnmount(() => { | ||
reaction.current.dispose(); | ||
}); | ||
// render the original component, but have the | ||
// reaction track the observables, so that rendering | ||
// can be invalidated (see above) once a dependency changes | ||
let rendering; | ||
reaction.current.track(() => { | ||
rendering = fn(); | ||
}); | ||
return rendering; | ||
} | ||
const Observer = observer(({ children, render }) => { | ||
// n.b. base case is not used for actual typings or exported in the typing files | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
if (isUsingStaticRendering()) { | ||
return baseComponent; | ||
} | ||
const realOptions = Object.assign({ forwardRef: false }, options); | ||
const baseComponentName = baseComponent.displayName || baseComponent.name; | ||
const wrappedComponent = (props, ref) => { | ||
return useObserver(() => baseComponent(props, ref), baseComponentName); | ||
}; | ||
// memo; we are not intested in deep updates | ||
// in props; we assume that if deep objects are changed, | ||
// this is in observables, which would have been tracked anyway | ||
let memoComponent; | ||
if (realOptions.forwardRef) { | ||
// we have to use forwardRef here because: | ||
// 1. it cannot go before memo, only after it | ||
// 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it | ||
// since it wouldn't be a callable function anymore | ||
memoComponent = react.memo(react.forwardRef(wrappedComponent)); | ||
} | ||
else { | ||
memoComponent = react.memo(wrappedComponent); | ||
} | ||
memoComponent.displayName = baseComponentName; | ||
return memoComponent; | ||
} | ||
function ObserverComponent({ children, render }) { | ||
const component = children || render; | ||
@@ -73,8 +126,9 @@ if (typeof component === "undefined") { | ||
return component(); | ||
}); | ||
Observer.displayName = "Observer"; | ||
Observer.propTypes = { | ||
} | ||
ObserverComponent.propTypes = { | ||
children: ObserverPropsCheck, | ||
render: ObserverPropsCheck | ||
}; | ||
ObserverComponent.displayName = "Observer"; | ||
const Observer = observer(ObserverComponent); | ||
function ObserverPropsCheck(props, key, componentName, location, propFullName) { | ||
@@ -102,4 +156,7 @@ const extraKey = key === "children" ? "render" : "children"; | ||
exports.useComputed = useComputed; | ||
exports.useObservableEffect = useObservableEffect; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.observer = observer; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.useObserver = useObserver; | ||
exports.Observer = Observer; |
{ | ||
"name": "mobx-react-lite", | ||
"version": "0.2.0", | ||
"version": "0.3.0", | ||
"description": "Lightweight React bindings for MobX based on experimental React hooks", | ||
@@ -20,3 +20,3 @@ "main": "dist/index.js", | ||
"test": "jest --watch", | ||
"test:travis": "yarn lint && jest", | ||
"test:travis": "yarn validate && yarn lint && jest", | ||
"build": "node build-rollup.js", | ||
@@ -50,4 +50,4 @@ "release": "np" | ||
"prettier": "^1.15.2", | ||
"react": "^16.7.0-alpha.0", | ||
"react-dom": "^16.7.0-alpha.0", | ||
"react": "^16.7.0-alpha.2", | ||
"react-dom": "^16.7.0-alpha.2", | ||
"react-testing-library": "^5.2.3", | ||
@@ -54,0 +54,0 @@ "regenerator-runtime": "^0.12.1", |
194
README.md
@@ -1,4 +0,5 @@ | ||
# mobx-react-lite | ||
# mobx-react-lite <!-- omit in toc --> | ||
[![Build Status](https://travis-ci.org/mobxjs/mobx-react-lite.svg?branch=master)](https://travis-ci.org/mobxjs/mobx-react) | ||
[![Join the chat at https://gitter.im/mobxjs/mobx](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mobxjs/mobx?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) | ||
@@ -8,3 +9,3 @@ | ||
**You need React version 16.7.0-alpha.0 which is highly experimental and not recommended for a production.** | ||
**You need React version 16.7.0-alpha.2 which is highly experimental and not recommended for production.** | ||
@@ -15,8 +16,65 @@ [![NPM](https://nodei.co/npm/mobx-react-lite.png)](https://www.npmjs.com/package/mobx-react-lite) | ||
- [API documentation](#api-documentation) | ||
- [`<Observer/>`](#observer) | ||
- [`observer<P>(baseComponent: FunctionComponent<P>, options?: IObserverOptions): FunctionComponent<P>`](#observerpbasecomponent-functioncomponentp-options-iobserveroptions-functioncomponentp) | ||
- [`useObserver<T>(fn: () => T, baseComponentName = "anonymous"): T`](#useobservertfn---t-basecomponentname--%22anonymous%22-t) | ||
- [`useObservable<T>(initialValue: T): T`](#useobservabletinitialvalue-t-t) | ||
- [`useComputed(func: () => T, inputs: ReadonlyArray<any> = []): T`](#usecomputedfunc---t-inputs-readonlyarrayany---t) | ||
- [`useObservableEffect<D extends IReactionDisposer>(disposerGenerator: () => D, inputs: ReadonlyArray<any> = []): D`](#useobservableeffectd-extends-ireactiondisposerdisposergenerator---d-inputs-readonlyarrayany---d) | ||
- [Server Side Rendering with `useStaticRendering`](#server-side-rendering-with-usestaticrendering) | ||
- [Why no Provider/inject?](#why-no-providerinject) | ||
- [What about smart/dumb components?](#what-about-smartdumb-components) | ||
## API documentation | ||
### observer(componentClass) | ||
### `<Observer/>` | ||
`Observer` is a React component, which applies `observer` to an anonymous region in your component. | ||
It takes as children a single, argumentless function which should return exactly one React component. | ||
The rendering in the function will be tracked and automatically re-rendered when needed. | ||
This can come in handy when needing to pass render function to external components (for example the React Native listview), or if you want to observe only relevant parts of the output for a performance reasons. | ||
```jsx | ||
import { Observer } from "mobx-react-lite" | ||
function ObservePerson(props) { | ||
const person = useObservable({ name: "John" }) | ||
return ( | ||
<div> | ||
{person.name} | ||
<Observer>{() => <div>{person.name}</div>}</Observer> | ||
<button onClick={() => (person.name = "Mike")}>No! I am Mike</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
[![Edit ObservePerson](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/jzj48v2xry?module=%2Fsrc%2FObservePerson.tsx) | ||
In case you are a fan of render props, you can use that instead of children. Be advised, that you cannot use both approaches at once, children have a precedence. | ||
Example | ||
```jsx | ||
import { Observer } from "mobx-react-lite" | ||
function ObservePerson(props) { | ||
const person = useObservable({ name: "John" }) | ||
return ( | ||
<div> | ||
{person.name} | ||
<Observer render={() => <div>{person.name}</div>} /> | ||
<button onClick={() => (person.name = "Mike")}>No! I am Mike</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
### `observer<P>(baseComponent: FunctionComponent<P>, options?: IObserverOptions): FunctionComponent<P>` | ||
Function that converts a function component into a reactive component, which tracks which observables are used automatically re-renders the component when one of these values changes. Observables can be passed through props, accessed from context or created locally with `useObservable`. | ||
As for options, it is an optional object with the following optional properties: | ||
- `forwardRef`: pass `true` to use [`forwardRef`](https://reactjs.org/docs/forwarding-refs.html) over the inner component, pass `false` (the default) otherwise. | ||
```tsx | ||
@@ -59,47 +117,24 @@ import { observer, useObservable } from "mobx-react-lite" | ||
### `Observer` | ||
### `useObserver<T>(fn: () => T, baseComponentName = "anonymous"): T` | ||
`Observer` is a React component, which applies `observer` to an anonymous region in your component. | ||
It takes as children a single, argumentless function which should return exactly one React component. | ||
The rendering in the function will be tracked and automatically re-rendered when needed. | ||
This can come in handy when needing to pass render function to external components (for example the React Native listview), or if you | ||
dislike the `observer` function. | ||
Low level implementation used internally by `observer`. | ||
It allows you to use an `observer` like behaviour, but still allowing you to optimize the component in any way you want (e.g. using `memo` with a custom `areEqual`, using `forwardRef`, etc.) and to declare exactly the part that is observed (the render phase). One good thing about this is that if any hook changes an observable for some reason then the component won't rerender twice unnecessarily. | ||
```jsx | ||
import { Observer } from "mobx-react-lite" | ||
```tsx | ||
import { memo } from "react" | ||
import { useObserver } from "mobx-react-lite" | ||
function ObservePerson(props) { | ||
const Person = memo(props => { | ||
const person = useObservable({ name: "John" }) | ||
return ( | ||
return useObserver(() => ( | ||
<div> | ||
{person.name} | ||
<Observer>{() => <div>{person.name}</div>}</Observer> | ||
<button onClick={() => (person.name = "Mike")}>No! I am Mike</button> | ||
</div> | ||
) | ||
} | ||
)) | ||
}) | ||
``` | ||
[![Edit ObservePerson](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/jzj48v2xry?module=%2Fsrc%2FObservePerson.tsx) | ||
### `useObservable<T>(initialValue: T): T` | ||
In case you are a fan of render props, you can use that instead of children. Be advised, that you cannot use both approaches at once, children have a precedence. | ||
Example | ||
```jsx | ||
import { Observer } from "mobx-react-lite" | ||
function ObservePerson(props) { | ||
const person = useObservable({ name: "John" }) | ||
return ( | ||
<div> | ||
{person.name} | ||
<Observer render={() => <div>{person.name}</div>} /> | ||
<button onClick={() => (person.name = "Mike")}>No! I am Mike</button> | ||
</div> | ||
) | ||
} | ||
``` | ||
### useObservable | ||
React hook that allows creating observable object within a component body and keeps track of it over renders. Gets all the benefits from [observable objects](https://mobx.js.org/refguide/object.html) including computed properties and methods. You can also use arrays and Map which are useful to track dynamic list/table of information. The Set is not supported (see https://github.com/mobxjs/mobx/issues/69). | ||
@@ -142,3 +177,3 @@ | ||
### useComputed | ||
### `useComputed(func: () => T, inputs: ReadonlyArray<any> = []): T` | ||
@@ -172,4 +207,37 @@ Another React hook that simplifies computational logic. It's just a tiny wrapper around [MobX computed](https://mobx.js.org/refguide/computed-decorator.html#-computed-expression-as-function) function that runs computation whenever observable values change. In conjuction with `observer` the component will rerender based on such a change. | ||
### Server Side Rendering with `useStaticRendering` | ||
[![Edit Calculator](https://codesandbox.io/static/img/play-codesandbox.svg)](https://codesandbox.io/s/jzj48v2xry?module=%2Fsrc%2FCalculator.tsx) | ||
### `useObservableEffect<D extends IReactionDisposer>(disposerGenerator: () => D, inputs: ReadonlyArray<any> = []): D` | ||
Adds an observable (Mobx) effect (`reaction`, `autorun`, `when`, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting. | ||
Returns the generated disposer for early disposal. | ||
Example (TypeScript): | ||
```typescript | ||
import { observer, useComputed, useObservableEffect } from "mobx-react-lite" | ||
const Name = observer((props: { firstName: string; lastName: string }) => { | ||
const fullName = useComputed(() => `${props.firstName} ${props.lastName}`, [ | ||
props.firstName, | ||
props.lastName | ||
]) | ||
// when the name changes then send this info to the server | ||
useObservableEffect(() => | ||
reaction( | ||
() => fullName, | ||
() => { | ||
// send this to some server | ||
} | ||
) | ||
) | ||
// render phase | ||
return `Your full name is ${props.firstName} ${props.lastName}` | ||
}) | ||
``` | ||
## Server Side Rendering with `useStaticRendering` | ||
When using server side rendering, the components are rendered only once. | ||
@@ -209,8 +277,50 @@ Since components are never unmounted, `observer` components would in this case leak memory when being rendered server side. | ||
} | ||
``` | ||
// a file with a component | ||
function ConnectedComponent() { | ||
// replacement for inject | ||
## What about smart/dumb components? | ||
The React hooks don't force anyone to suddenly have a state inside a _dumb component_ that is supposed to only render stuff. You can separate your concerns in a similar fashion. | ||
```tsx | ||
import { createSelector } from 'react-selector-hooks' | ||
const userSelector = createSelector(({ user ) => ({ | ||
name: user.name, | ||
age: user.age | ||
})) | ||
function UiComponent({ name, age }) { | ||
return ( | ||
<div> | ||
<div>Name: {name}</div> | ||
<div>Age: {age}</div> | ||
</div> | ||
) | ||
} | ||
export default () => { | ||
// you may extract these two lines into a custom hook | ||
const store = useContext(StoreContext) | ||
const data = userSelector(store) | ||
return UiComponent({...data}) | ||
// perhaps wrap it inside observer in here? | ||
return observer(UiComponent({...data})) | ||
} | ||
``` | ||
It may look a bit more verbose than a _classic_ inject, but there is nothing stopping you to make your own `inject` HOC which is so much easier since everything is just a funciton. | ||
```tsx | ||
// make universal HOC | ||
function inject(useSelector, baseComponent) { | ||
const store = useContext(StoreContext) | ||
const selected = useSelector(store) | ||
// optional memo essentially making a pure component | ||
return React.memo(props => baseComponent({ ...selected, ...props })) | ||
} | ||
// use the HOC with a selector | ||
export default inject(userSelector, UiComponent) | ||
``` |
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
270826
36
799
321
1