mobxjs / mobx-react Goto Github PK
View Code? Open in Web Editor NEWReact bindings for MobX
Home Page: https://mobx.js.org/react-integration.html
License: MIT License
React bindings for MobX
Home Page: https://mobx.js.org/react-integration.html
License: MIT License
So I was switching my project to typescript and in this process I got Cannot read property 'render' of undefined
.
After following the stack trace, I ended up in this line.
Debugging showed that componentClass.prototype
was undefined, causing it to throw on trying to get a property on undefined. Changing the line to the below fixed everything and all components worked.
if (typeof componentClass === "function" && !componentClass.prototype && !componentClass.isReactClass && !React.Component.isPrototypeOf(componentClass)) {
This is the stack trace, tsconfig, and relevant files
Im not sure if this is a bug on my end or on mobx-react. Right now the "fix" I am using is replacing the line of code, but that isn't very maintainable.
Really like what mobx
is offering. Thank you for sharing.
My first attempt to try the mobx
is integrating it with react native app that uses react-native-router-flux
. Can't seem to have binding working, pretty sure i am missing something:
1. Router
class RootRouter extends Component {
render() {
return(
<Drawer content={<ControlPanel />}>
<View style={layout.layout}>
<Router hideNavBar={true} >
<Schema name="default" sceneConfig={Navigator.SceneConfigs.FloatFromRight}/>
<Route name="inventoryList" type="replace" wrapRouter={false} component={InventoryList} title="Inventory" />
</Router>
</View>
</Drawer>
);
}
}
2. Main component
class ControlPanel extends Component {
constructor(props) {
super(props);
InventoryStore.httpReq();
}
showInventoryList() {
Actions.inventoryList({store:InventoryStore });
}
render() {
return (
<View>
<ScrollView>
<View>
<TouchableOpacity onPress={this.showInventoryList}>
<View>
<Text>Inventory List</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView>
</View>
);
}
}
3. The List component
class InventoryList extends Component {
render() {
if (this.props.store.isLoading === true) {
return <View><Text>Loading ...</Text></View>;
}
return (
<View>
{ this.props.store.inventoryData.map(
(inventory, idx) => <InventoryView inventory={ inventory } key={ idx } />
) }
</View>
);
}
}
@observer
class InventoryView extends Component {
render() {
const inventory = this.props.inventory;
return (
<View>
<Text>{inventory.ProjectNumber}</Text>
</View>
);
}
}
4. The store
const InventoryStore = observable({
inventoryData: [],
isLoading: true
});
InventoryStore.httpReq = function (model) {
this.isLoading = true;
fetch('http://www.example.com/api/GetData', {
method: 'GET',
})
.then((response) => response.json())
.then((responseData) => {
this.isLoading = false;
this.inventoryData = responseData
})
.done();
}
Any idea?
I have a container component makes about 4 ajax calls after it loads. The calls shoudl update each view accordingly, but it doesn't.
It appears that render is not called when data is received from the server, because when I go to a different route and then come back to this container, the data does show in the view.
Perhaps a complete example of async actions would be helpful.
It is a lot of code, so I will just post here the revelant components
Component that does not update
import React, { PropTypes } from 'react';
import BootTable from './common/boot-table';
import { fetch_trace } from '../stores/trace';
class TraceContainer extends React.Component {
componentWillMount() {
fetch_trace(this.props.connectionID);
}
render () {
const colHeaders = [
{name: 'traceDateTime', label: 'Date/Time'},
{name: 'traceAction', label: 'Type'},
{name: 'traceText', label: 'Message'}
];
const { connectionID } = this.props;
// console.log(this.props.connectionID);
// console.log(this.context.store.trace.connections[connectionID]);
const lines = (this.context.store.trace.connections) ? this.context.store.trace.connections[connectionID] : [];
return (
<div className="trace">
<BootTable
bordered
condensed
perPage={10}
hover
data={this.context.store.trace.connections[connectionID]}
colHeaders={colHeaders}
/>
</div>);
}
}
TraceContainer.contextTypes = {
store: PropTypes.object
};
export default TraceContainer;
Contaner component
import React, { PropTypes } from 'react';
import {Tab, Tabs, Label } from 'react-bootstrap';
import { observer } from 'mobservable-react';
import { fetch_hosts } from '../stores/hosts';
import TraceContainer from './trace-container';
const PowerLight = (props) => {
const light = (props.status == "1") ? <i className="fa fa-circle"></i> : <i className="fa fa-circle-o"></i>;
return <span className="host-status">{light}</span>;
};
const ConnectionInfo = (props) => {
const { ConnectionName, IPaddress, IsServer, NumLineTrace, TCPPort, TimeOut } = props;
const type = (IsServer == "1") ? "Server":"Client";
return <div className="connection-info">
<ul className="list-inline">
<li><Label>Connection Name</Label> {ConnectionName}</li>
<li><Label>IP</Label> {IPaddress}</li>
<li><Label>Type</Label> {type}</li>
<li><Label>Port</Label> {TCPPort}</li>
<li><Label>Lines</Label> {NumLineTrace}</li>
<li><Label>Timeout</Label> {TimeOut}</li>
</ul>
</div>;
};
class HostTabsContainer extends React.Component {
componentDidMount() {
fetch_hosts();
}
renderTabs(hosts) {
return hosts.map((host,i) => {
const { ID, ConnectionName, Trace } = host;
const title = <div><PowerLight status={Trace}/> {ConnectionName}</div>;
return <Tab key={i} eventKey={ID} title={title}>
<ConnectionInfo {...host}/>
<TraceContainer connectionID={ID}/>
</Tab>;
});
}
render () {
const { hosts } = this.context.store.host;
return (
<div className="host-tabs">
<Tabs defaultActiveKey={1} animation={false}>
{this.renderTabs(hosts)}
</Tabs>
</div>);
}
}
HostTabsContainer.contextTypes = {
store: PropTypes.object
};
export default observer(HostTabsContainer);
Main Store
import { observable, autorun } from 'mobservable';
import trace from './stores/trace';
import hosts from './stores/hosts';
import navigation from './stores/navigation';
const appState = observable({
host: hosts,
trace: trace,
navigation: navigation
});
/* a function that observes the state */
autorun(function() {
console.log(appState);
});
export default appState;
Store that has data relevant to the component
import request from 'superagent';
import _ from 'lodash';
import { observable, autorun } from 'mobservable';
const URL = 'TRACER.wsTraceHCA.cls';
const traceStore = {
isLoading: true,
connections: {}
};
export default traceStore;
// ** ACTIONS
export function fetch_trace(connectionID) {
if (!connectionID) {
return;
}
const query = {
connectionID
};
request.get(URL)
.query(query)
.end(function(err,res){
if (err) {
console.warn("An error ocurred: ", err);
}
else {
const lines = res.body;
if (typeof(traceStore.connections.connectionID) == 'undefined') {
traceStore.connections[connectionID] = [];
}
// traceStore.connections[connectionID] = [];
traceStore.connections[connectionID] = traceStore.connections[connectionID].concat(lines);
traceStore.isLoading = false;
}
});
}
What is the best/recommended way to pass a store
down a nested component hierarchy if I don't want to use a global
object for that?
Is there something like the <Provider store={myStore}>
mechanism from react-redux
. I haven't found anything related to that neither in the docs nor in the test suite...
I can't install mobx-react on Windows 7 Enterprise SP1. I can install it on OS X but not on Windows.
>npm install mobx-react --save
npm ERR! Windows_NT 6.1.7601
npm ERR! argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" "mobx-react" "--save" npm ERR! node v6.1.0
npm ERR! npm v3.8.6
npm ERR! code ENOSELF
npm ERR! Refusing to install mobx-react as a dependency of itself
npm ERR!
npm ERR! If you need help, you may report this error at:
npm ERR! <https://github.com/npm/npm/issues>
npm ERR! Please include the following file with any support request:
npm ERR! C:\Users\hipertracker\IdeaProjects\mobx-react\npm-debug.log
npm-debug.log:
0 info it worked if it ends with ok
1 verbose cli [ 'C:\\Program Files\\nodejs\\node.exe',
1 verbose cli 'C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js',
1 verbose cli 'install',
1 verbose cli 'mobx-react',
1 verbose cli '--save' ]
2 info using [email protected]
3 info using [email protected]
4 silly loadCurrentTree Starting
5 silly install loadCurrentTree
6 silly install readLocalPackageData
7 silly fetchPackageMetaData mobx-react
8 silly fetchNamedPackageData mobx-react
9 silly mapToRegistry name mobx-react
10 silly mapToRegistry using default registry
11 silly mapToRegistry registry http://registry.npmjs.org/
12 silly mapToRegistry data Result {
12 silly mapToRegistry raw: 'mobx-react',
12 silly mapToRegistry scope: null,
12 silly mapToRegistry name: 'mobx-react',
12 silly mapToRegistry rawSpec: '',
12 silly mapToRegistry spec: 'latest',
12 silly mapToRegistry type: 'tag' }
13 silly mapToRegistry uri http://registry.npmjs.org/mobx-react
14 verbose request uri http://registry.npmjs.org/mobx-react
15 verbose request no auth needed
16 info attempt registry request try #1 at 3:56:28 PM
17 verbose request id 682d08b0672ae9fc
18 verbose etag "4Y5349DQ1M6XWB3P1ZMCABHTT"
19 http request GET http://registry.npmjs.org/mobx-react
20 info retry will retry, error on last attempt: Error: unexpected end of file
21 info attempt registry request try #2 at 3:56:38 PM
22 verbose etag "4Y5349DQ1M6XWB3P1ZMCABHTT"
23 http request GET http://registry.npmjs.org/mobx-react
24 http 304 http://registry.npmjs.org/mobx-react
25 verbose headers { server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)',
25 verbose headers etag: '"4Y5349DQ1M6XWB3P1ZMCABHTT"',
25 verbose headers 'content-type': 'application/json',
25 verbose headers 'cache-control': 'max-age=300',
25 verbose headers 'accept-ranges': 'bytes',
25 verbose headers date: 'Fri, 13 May 2016 14:56:38 GMT',
25 verbose headers via: '1.1 varnish',
25 verbose headers 'x-served-by': 'cache-jfk1036-JFK',
25 verbose headers 'x-cache': 'MISS',
25 verbose headers 'x-cache-hits': '0',
25 verbose headers 'x-timer': 'S1463151398.759860,VS0,VE33',
25 verbose headers vary: 'Accept-Encoding',
25 verbose headers connection: 'Keep-Alive',
25 verbose headers age: '0' }
26 silly get cb [ 304,
26 silly get { server: 'CouchDB/1.5.0 (Erlang OTP/R16B03)',
26 silly get etag: '"4Y5349DQ1M6XWB3P1ZMCABHTT"',
26 silly get 'content-type': 'application/json',
26 silly get 'cache-control': 'max-age=300',
26 silly get 'accept-ranges': 'bytes',
26 silly get date: 'Fri, 13 May 2016 14:56:38 GMT',
26 silly get via: '1.1 varnish',
26 silly get 'x-served-by': 'cache-jfk1036-JFK',
26 silly get 'x-cache': 'MISS',
26 silly get 'x-cache-hits': '0',
26 silly get 'x-timer': 'S1463151398.759860,VS0,VE33',
26 silly get vary: 'Accept-Encoding',
26 silly get connection: 'Keep-Alive',
26 silly get age: '0' } ]
27 verbose etag http://registry.npmjs.org/mobx-react from cache
28 verbose get saving mobx-react to C:\Users\zk4lo9p\AppData\Roaming\npm-cache\registry.npmjs.org\mobx-react\.cache.json
29 verbose correctMkdir C:\Users\zk4lo9p\AppData\Roaming\npm-cache correctMkdir not in flight; initializing
30 silly install normalizeTree
31 silly loadCurrentTree Finishing
32 silly loadIdealTree Starting
33 silly install loadIdealTree
34 silly cloneCurrentTree Starting
35 silly install cloneCurrentTreeToIdealTree
36 silly cloneCurrentTree Finishing
37 silly loadShrinkwrap Starting
38 silly install loadShrinkwrap
39 silly loadShrinkwrap Finishing
40 silly loadAllDepsIntoIdealTree Starting
41 silly install loadAllDepsIntoIdealTree
42 silly rollbackFailedOptional Starting
43 silly rollbackFailedOptional Finishing
44 silly runTopLevelLifecycles Starting
45 silly runTopLevelLifecycles Finishing
46 silly install printInstalled
47 verbose stack Error: Refusing to install mobx-react as a dependency of itself
47 verbose stack at checkSelf (C:\Program Files\nodejs\node_modules\npm\lib\install\validate-args.js:53:14)
47 verbose stack at Array.<anonymous> (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\bind-actor.js:15:8)
47 verbose stack at LOOP (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\chain.js:15:14)
47 verbose stack at chain (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\chain.js:20:5)
47 verbose stack at C:\Program Files\nodejs\node_modules\npm\lib\install\validate-args.js:16:5
47 verbose stack at C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\async-map.js:52:35
47 verbose stack at Array.forEach (native)
47 verbose stack at C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\async-map.js:52:11
47 verbose stack at Array.forEach (native)
47 verbose stack at asyncMap (C:\Program Files\nodejs\node_modules\npm\node_modules\slide\lib\async-map.js:51:8)
48 verbose cwd C:\Users\zk4lo9p\IdeaProjects\mobx-react
49 error Windows_NT 6.1.7601
50 error argv "C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\nodejs\\node_modules\\npm\\bin\\npm-cli.js" "install" "mobx-react" "--save"
51 error node v6.1.0
52 error npm v3.8.6
53 error code ENOSELF
54 error Refusing to install mobx-react as a dependency of itself
55 error If you need help, you may report this error at:
55 error <https://github.com/npm/npm/issues>
56 verbose exit [ 1, true ]
Does this lib work with React Native? If not, any idea what it would take to make it work?
Hey guys,
Is there currently any way to prevent rendering ?
I would need such a feature to optimize my server side rendering, let me explain how it works:
1/ I'm rendering my app in order to feed the stores (API call...)
2/ When all the stores are ready, I'm rendering my app a second time in order to get the final html
3/ I'm sending the html
With this feature I would be able to prevent the components rendering in the part 1/ for performance improvements ! : )
Do you think we could implement this feature ? I guess we may only have to add a condition in the shouldComponentUpdate lifecycle method ?
Thanks
The reportRendering method use WeakMap which is not available in IE 10 and the method fails. Preparing simple PR for that.
I have the following (toy) component to use in a table:
@observer
class ProgressPus extends Component<{ frac: number }, {}> {
render() {
return <div>
<p>{this.props.frac}%</p>
</div>
}
}
I define it in the same module as my table, and use it in a table cell like this:
<td><ProgressPus frac={item.progress}/></td>
and everything works fine. However, if I move ProgressPus
to a different tsx file and import it into the table file with import {ProgressPus} from 'otherFIle'
, it now gives the error below. Should I be using a different syntax to import an @observer component?
[mobservable.view 'reactiveRender'] There was an uncaught error during the computation of ComputedObservable[reactiveRender (current value:'undefined')] function reactiveRender() {
if (!hasRendered) {
hasRendered = true;
// withStrict: throw errors if the render function tries to alter state.
mobservable.extras.withStrict(true, function() {
rendering = baseRender.call(self);
});
} else {
self.__$mobRenderDisposer(); // dispose
React.Component.prototype.forceUpdate.call(self);
}
}ViewNode.computeNextState @ dnode.js:204ViewNode.wakeUp @ dnode.js:168ViewNode.setRefCount @ dnode.js:150(anonymous function) @ core.js:89runAfterTransaction @ dnode.js:44autorun @ core.js:87render @ index.js:51ReactCompositeComponentMixin._renderValidatedComponentWithoutOwnerOrContext @ ReactCompositeComponent.js:587ReactCompositeComponentMixin._renderValidatedComponent @ ReactCompositeComponent.js:607wrapper @ ReactPerf.js:66ReactCompositeComponentMixin.mountComponent @ ReactCompositeComponent.js:220wrapper @ ReactPerf.js:66ReactReconciler.mountComponent @ ReactReconciler.js:37ReactMultiChild.Mixin.mountChildren @ ReactMultiChild.js:241ReactDOMComponent.Mixin._createContentMarkup @ ReactDOMComponent.js:591ReactDOMComponent.Mixin.mountComponent @ ReactDOMComponent.js:479ReactReconciler.mountComponent @ ReactReconciler.js:37ReactMultiChild.Mixin.mountChildren @ ReactMultiChild.js:241ReactDOMComponent.Mixin._createContentMarkup @ ReactDOMComponent.js:591ReactDOMComponent.Mixin.mountComponent @ ReactDOMComponent.js:479ReactReconciler.mountComponent @ ReactReconciler.js:37ReactCompositeComponentMixin.mountComponent @ ReactCompositeComponent.js:225wrapper @ ReactPerf.js:66ReactReconciler.mountComponent @ ReactReconciler.js:37ReactMultiChild.Mixin._mountChildByNameAtIndex @ ReactMultiChild.js:474ReactMultiChild.Mixin._updateChildren @ ReactMultiChild.js:378ReactMultiChild.Mixin.updateChildren @ ReactMultiChild.js:326ReactDOMComponent.Mixin._updateDOMChildren @ ReactDOMComponent.js:871ReactDOMComponent.Mixin.updateComponent @ ReactDOMComponent.js:700ReactDOMComponent.Mixin.receiveComponent @ ReactDOMComponent.js:645ReactReconciler.receiveComponent @ ReactReconciler.js:87ReactChildReconciler.updateChildren @ ReactChildReconciler.js:84ReactMultiChild.Mixin._reconcilerUpdateChildren @ ReactMultiChild.js:216ReactMultiChild.Mixin._updateChildren @ ReactMultiChild.js:351ReactMultiChild.Mixin.updateChildren @ ReactMultiChild.js:326ReactDOMComponent.Mixin._updateDOMChildren @ ReactDOMComponent.js:871ReactDOMComponent.Mixin.updateComponent @ ReactDOMComponent.js:700ReactDOMComponent.Mixin.receiveComponent @ ReactDOMComponent.js:645ReactReconciler.receiveComponent @ ReactReconciler.js:87ReactChildReconciler.updateChildren @ ReactChildReconciler.js:84ReactMultiChild.Mixin._reconcilerUpdateChildren @ ReactMultiChild.js:216ReactMultiChild.Mixin._updateChildren @ ReactMultiChild.js:351ReactMultiChild.Mixin.updateChildren @ ReactMultiChild.js:326ReactDOMComponent.Mixin._updateDOMChildren @ ReactDOMComponent.js:871ReactDOMComponent.Mixin.updateComponent @ ReactDOMComponent.js:700ReactDOMComponent.Mixin.receiveComponent @ ReactDOMComponent.js:645ReactReconciler.receiveComponent @ ReactReconciler.js:87ReactCompositeComponentMixin._updateRenderedComponent @ ReactCompositeComponent.js:562ReactCompositeComponentMixin._performComponentUpdate @ ReactCompositeComponent.js:544ReactCompositeComponentMixin.updateComponent @ ReactCompositeComponent.js:473wrapper @ ReactPerf.js:66ReactCompositeComponentMixin.performUpdateIfNecessary @ ReactCompositeComponent.js:421ReactReconciler.performUpdateIfNecessary @ ReactReconciler.js:102runBatchedUpdates @ ReactUpdates.js:129Mixin.perform @ Transaction.js:136Mixin.perform @ Transaction.js:136assign.perform @ ReactUpdates.js:86flushBatchedUpdates @ ReactUpdates.js:147wrapper @ ReactPerf.js:66Mixin.closeAll @ Transaction.js:202Mixin.perform @ Transaction.js:149ReactDefaultBatchingStrategy.batchedUpdates @ ReactDefaultBatchingStrategy.js:62enqueueUpdate @ ReactUpdates.js:176enqueueUpdate @ ReactUpdateQueue.js:24ReactUpdateQueue.enqueueForceUpdate @ ReactUpdateQueue.js:143ReactComponent.forceUpdate @ ReactComponent.js:86reactiveRender @ index.js:60ObservableView.compute @ observableview.js:57(anonymous function) @ dnode.js:198withStrict @ core.js:301ViewNode.computeNextState @ dnode.js:197ViewNode.notifyStateChange @ dnode.js:182DataNode.notifyObservers @ dnode.js:113DataNode.markReady @ dnode.js:107transaction @ dnode.js:34ListDataModelObservable.setData @ listDataModelObservable.ts:36(anonymous function) @ choresByReact.tsx:237Talker.processHoot @ talker.ts:26(anonymous function) @ talker.ts:11(anonymous function) @ talker.ts:43fire @ jquery.js:3100self.fireWith @ jquery.js:3212done @ jquery.js:8265(anonymous function) @ jquery.js:8606
dnode.js:204 [mobservable.view 'TableView#.1.render()'] There was an uncaught error during the computation of ComputedObservable[TableView#.1.render() (current value:'undefined')] function reactiveRender() {
if (!hasRendered) {
hasRendered = true;
// withStrict: throw errors if the render function tries to alter state.
mobservable.extras.withStrict(true, function() {
rendering = baseRender.call(self);
});
} else {
self.__$mobRenderDisposer(); // dispose
React.Component.prototype.forceUpdate.call(self);
}
}
Anyone got Jest working?
Hey. I'm trying out mobservable-react-typescript and I can't get the typescript compiler to accept "const TimerObserver = observer(Timer)" instead of @observer on Timer
I'm getting "Error:(22, 10) TS2604: JSX element type 'TimerObserver' does not have any construct or call signatures." when I try to use <TimerObserver />
If I cast it to any, the code works, just like the JS examples: const TimerObserver = observer(Timer) as any;
Hmm. Appears to be an issue with overload resolution in TypeScript. If I comment out all but the React.ComponentClass<P> overload it compiles.
TypeScript 1.8 beta(edited)
If I move the following overload to the top it works: export function observer<P>(clazz:
React.ComponentClass<P>): React.ComponentClass<P>;
npm installed mobservable-react-typescript.. got mobservable 1.2.5 and mobservable-react 2.1.4
onCanvasClick (in canvas.js) is not invoked on mouse click. Works fine when I downgrade to mobx-react 3.0.3.
Here's my package versions:
node 4.2.2
npm 2.14.7
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
โโโ [email protected]
Hi,
I have a store with an observable list of items. Each item has an id and an observable value.
In the react part I have a List component and a ListItem component. The ListItem component renders the itemโs id and value. Next to the value there is a +1 button that increases the itemโs value by 1.
The List and ListItem components are observers.
Without propTypes definitions everything is working as expected. If I add a new item to the list only the List component is updated and if I click the +1 button only that specific item is updated.
If I add propTypes definitions to the List and ListItem components on each +1 button click the List component is also updated for some reason. But if I remove the observable properties from the propTypes definition of the ListItem component everything is working as expected.
Example: https://jsfiddle.net/ppetrov/e6ukjvxo/
Every component has a console.log in its render method.
Am I doing something wrong?
Best regards
Uncaught TypeError: Cannot read property 'forEach' of undefined
Error references to line 94 in node_modules/mobservable-react/index.js
, that states
this.__$mobDependencies.forEach(function(dep) {
dep.setRefCount(-1);
});
This happens when component has @observer
decorator and has own componentWillMount()
defined but does not have own componentWillUnmount()
. Adding empty componentWillUnmount()
method to component makes the error go away.
Could you please update so we can use it with mobservable 0.7.0 ?
Thanks!
npm ERR! argv "/usr/local/Cellar/node/4.1.0/bin/node" "/usr/local/bin/npm" "install" "-d"
npm ERR! node v4.1.0
npm ERR! npm v2.14.5
npm ERR! code EPEERINVALID
npm ERR! peerinvalid The package [email protected] does not satisfy its siblings' peerDependencies requirements!
npm ERR! peerinvalid Peer [email protected] wants mobservable@^0.6.9
React docs warn against using forceUpdate
to update a component because it is not as efficient.
https://facebook.github.io/react/docs/component-api.html#forceupdate
mobx-react is using this API to react to observable changes: https://github.com/mobxjs/mobx-react/blob/master/index.js#L53
Would it be worth exploring alternatives to forceUpdate, such as forcing rerendering through something like setState?
http://jsbin.com/gegagu/5/edit?js,output
In this example, you may click on the steps to activate them. You'll see that the active step at the top is updating once per transaction change. When you click the buttons below the steps, you'll see that the active step is updating multiple times.
I made a simple application that has a store structured something like this:
{
tableData: []
,sortColumn: 0
,headerTypes: [Number, Date, String, String, String]
}
All of that is observable. I then made a computed view of the table data:
@computed get sortedData() {
return this.tableData.sort(this.getSortFn(this.sortType, this.sortColumn));
}
I then fill the tableData with ~10000 items that contains data like:
[0, new Date(), "foo", "foo", "foo"]
and I render it out via:
import React, { Component, PropTypes } from 'react';
import {observer} from "mobs-react";
import Row from './Flexbox/Row.jsx';
import Col from './Flexbox/Col.jsx';
import Box from './Flexbox/Box.jsx';
import DemoListItem from './DemoListItem.jsx';
import store from '../Store/DemoTableStore.jsx';
observer
class DemoTableListItemsView extends Component {
render() {
let table = [];
const { tableData } = this.props;
for(var i=0, end=tableData.length; i<end; ++i) {
table.push(<DemoListItem key={tableData[i][0]} data={tableData[i]}/>);
}
return <div>{table}</div>;
}
}
@observer
class DemoTable extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div style={{ margin: 20, backgroundColor: '#E4E4E4' }}>
<Row style={{ width: '100%', left: 16, backgroundColor: '#A4A4A4', margin: 0 }}>
<Col className="col-xs">
<Box border={0}>
<p style={{ textAlign: 'center', borderRadius: '0.2em' }}>
<span>Number</span>
</p>
</Box>
</Col>
<Col className="col-xs">
<Box>
<p style={{ textAlign: 'center', borderRadius: '0.2em' }}>
<span>Date</span>
</p>
</Box>
</Col>
<Col className="col-xs">
<Box >
<p style={{ textAlign: 'center', borderRadius: '0.2em' }}>
<span >String</span>
</p>
</Box>
</Col>
<Col className="col-xs">
<Box >
<p style={{ textAlign: 'center', borderRadius: '0.2em' }}>
<span >Info1</span>
</p>
</Box>
</Col>
<Col className="col-xs">
<Box >
<p style={{ textAlign: 'center', borderRadius: '0.2em' }}>
<span >Info2</span>
</p>
</Box>
</Col>
</Row>
{/*Recommended pattern by mobx, rendering a list with a component who's only job is to render the list*/}
<DemoTableListItemsView tableData={store.sortedData}/>
</div>
);
}
}
export default DemoTable;
Trying to change my sort column, I get this spewing out from mobx:
[Error] RangeError: Maximum call stack size exceeded.
quickDiff (bundle.js:39528)
bindDependencies (bundle.js:38499)
trackDerivedFunction (bundle.js:38485)
trackAndCompute (bundle.js:38425)
onDependenciesReady (bundle.js:38399)
notifyDependencyReady (bundle.js:38468)
(anonymous function) (bundle.js:38586:93)
forEach
propagateReadiness (bundle.js:38586)
propagateAtomReady (bundle.js:38314)
reportReady (bundle.js:38350)
reportChanged (bundle.js:38335)
set (bundle.js:39349)
(anonymous function) (bundle.js:39297)
sortColumn (bundle.js:147)
(anonymous function) (bundle.js:203)
handleMouseUp (bundle.js:15937)
(anonymous function)
dispatchEvent
invokeGuardedCallback (bundle.js:50174)
executeDispatch (bundle.js:42419)
executeDispatchesInOrder (bundle.js:42439)
executeDispatchesAndRelease (bundle.js:41890)
executeDispatchesAndReleaseTopLevel (bundle.js:41901)
forEach
forEachAccumulated (bundle.js:56402)
processEventQueue (bundle.js:42063)
runEventQueueInBatch (bundle.js:50201)
handleTopLevel (bundle.js:50212)
handleTopLevelImpl (bundle.js:50290)
perform (bundle.js:55852)
batchedUpdates (bundle.js:48863)
batchedUpdates (bundle.js:53572)
dispatchEvent (bundle.js:50367)
(anonymous function)
I understand 10000 rows is a lot, but why would it only explode on a computed sort? I can keep inserting more elements without any issues. Let me know if I need to be clearer or if more code is needed.
Should I use mobservable-react
with the mobx
package for now?
Update: It seems that mobx
package is not ready either. I will stick with mobservable-*
packages.
Hello!
mobx-react do not track dependencies between components contained in each other
so evaluations of computed props they use are not ordered by mobx stale/ready algorithm.
This means that mobx-react's forceUpdate of parent component can eventually
call render method of child component while it's used @computed
's are stale.
More precisely, suppose we have two components A and B where A uses B in it's render metod.
There is also Store with two @computed
properties x and y both depending on @observable
z.
Component A uses x, component B uses y. Now when z changes the following can happen:
z changes -> x evaluated -> forceUpdate A -> render B -> use y which is stale.
Computed properties x and y are not completely pure in my code, they
can make ajax requests, see this for the reasons.
The problem is that component B queries y multiple times, so many equal ajax
requests can be made. I admit that this violates mobx requirement for @computed
props
to be absolutely pure functions, but maybe there is some workaround ? :)
All my other code is very neat without autorun's. And as another argument: many reevaluations of pure functions can also be an overhead.
(great work for mobx, @mweststrate, thank you for sharing it!)
Are there some more debugging aids available hidden somewhere inside the code?
I would like to track down, why a react
component doesn't get updated automatically although it displays an observable value. How would I track down the issue best?
I haven't managed to get a down-stripped version of my issue yet. The following is a small example that is very similar to my case, but this is working as expected:
import React from 'react';
import mobs from 'mobservable';
import mobsr from 'mobservable-react';
require("mobservable-react-devtools");
const tree = mobs.observable(
{ name: 'root_name', data: 1, collapsed: false, children: [
{ name: 'child_1', data: 2 },
{ name: 'child_2', data: 3, collapsed: false, children: [
{ name: 'child_2_1', data: 4 }
] }
] }
);
const TreeComponent = mobsr.observer(({ tree }) => {
const hasChildren = tree.children && tree.children.length;
const openClose = hasChildren ? <span>{tree.collapsed ? '[OPEN] ' : '[CLOSE] '}</span> : null;
const showChildren = hasChildren && !tree.collapsed;
return <div>
<li><span>{openClose}{tree.name}: {tree.data}</span></li>
{showChildren && <ul>{tree.children.map((c, idx) => <TreeComponent key={idx} tree={c} />)}</ul>}
</div>;
});
ReactDOM.render(
<ul><TreeComponent tree={tree} /></ul>,
document.getElementById('app_id')
);
setInterval(() => { tree.children[0].data = Math.random(); }, 3000);
setInterval(() => { tree.children[1].name = Math.random(); }, 4500);
setInterval(() => { tree.collapsed = !tree.collapsed; }, 7000);
setInterval(() => { tree.children[1].collapsed = !tree.children[1].collapsed; }, 4000);
My case is very similar, except that it doesn't react to changes of collapsed
automatically. I need to add the following propTypes
definition to make it actually working:
TreeComponent.propTypes = {
tree: React.PropTypes.shape({
collapsed: React.PropTypes.bool
})
}
The bad thing about that is, it stops working again, when switching to production
mode, since then react
ignores the propTypes
definitions.
How would/could I track down the issue best?
Hi I am storing an object in an mobservable store that represents a React style
object so that I can update styles from within my app. When I update it , e.g. change the borderColor
and my React component re-renders nothing changes. If I transfer the style
props to a regular JS object manually it works.
const theStyle = {...store.currentStyle}
Any idea why?
I am finding the behaviour to be rather inconsistent, and I think it may have something to do with react-router. I have store variables that when modified, trigger render
with the modified values and my views do the right thing, but in some cases that I have not quite pinned down the reason, render
gets called but the values in the store don't change. The following is an example of this
Here's my store:
export default class {
@observable loggedIn = auth.loggedIn()
}
export let connect = Store => Composed =>
class extends Component {
render() {
return <Composed { ...this.props } $ = { new Store() } />
}
}
Root component passed to react-router
@connect(Store)
@observer
export default class App extends Component {
logout = () => {
this.context.router.replace(`/`)
this.props.$.loggedIn = false
console.log('logout', this.props.$.loggedIn) // onClick triggers this first and logs false
};
render() {
let children = Children.map(this.props.children, child => {
return cloneElement(child, {
...child.props,
...this.props,
logout: this.logout,
})
})
console.log('render', this.props.$.loggedIn) // true
return <div>{ children }</div>
}
}
...and lastly child component with click method:
export default observer(({
$,
logout,
}) =>
<div>
{ $.loggedIn &&
<div>
<a onClick={ logout }>Log out</a>
</div>
}
</div>
)
Most of the time everything works.. .but this does not! Everytime I click logout
, the loggedIn
observable logs false
, triggers render, and inside render remains true
A common pattern I see arising is the need to connect a component to one or many mobx stores.
Currently, you need to write your own higher order component that will fetch these stores. While this isn't a monumental task in itself (as mobx makes this type of thing relatively straight forward), it would be great to have this feature built into mobx-react to promote a standardized method to accomplish this.
Looking to react-redux for inspiration, I see a lot of ideas there that could gracefully transfer over to mobx-react.
One approach is to wrap the application's Root component in a higher order store provider component that will inject application stores into the application's context. Then retrieve stores with an @connect
(or similar) decorator that defines which stores should be connected.
Another approach is to "register" stores with react-mobx at their definition. Then, use a similar @connect
decorator as described above to fetch one or several registered stores.
I would like to start a discussion as to why this does/doesn't make sense for mobx-react. And if it does make sense, get other thoughts on the table.
This sucks badly if you seriously use PropTypes.
I tried this on stateless component, but I think this bug might occur for normal components as well.
Hi! Thanks for the great lib but it's very inconvenient to track changes between versions.
It would be great either to add CHANGELOG.md
or attach this info to new releases.
Thanks!
Had a hard time today figuring out why a very simple mobx react sandbox app did not work.
Turned out that the issue was due to the order of babel plugins in my .babelrc
.
transform-decorators-legacy
has to appear first in the list, otherwise it will break with at least either transform-function-bind
or transform-class-properties
plugins if they defined before it, with the wrong orders, observables are not triggered at all.
Would be great to update README to clarify this.
Hi,
I have a little component that wraps a button and shows up a "Loading" text if you click the button.
Something like that:
import React, { Component } from 'react';
import { observer } from 'mobx-react';
import { observable } from 'mobx';
@observer
export class AsyncButton extends Component<void, {}> {
@observable
private isLoading: boolean = false;
constructor() {
super();
console.log('constructor')
}
render () {
console.log('render', this.isLoading);
return (
<Button
onClick={() => this.isLoading = !this.isLoading}>
{this.isLoading ? <span>Loading</span> : <span>Click me</span>}
</Button>
);
}
}
ReactDOM.render(
<div>
<AsyncButton/>
<AsyncButton/>
</div>,
document.getElementById('root'));
Expected result: two buttons, If I click button 1 text changes to "Loading" and console logs "render,true"
Actual result: two buttons, If I click button 1 text of button 1 and button 2 changes to "Loading" and console logs "render,true" twice. If I click button 1 again both buttons are re-rendered and text changes to "Click me".
Workarounds:
Im using Typescript 1.8.9 (with experimentalDecorators set to true), mobx 2.0.4 and mobx-react 3.0.3
More info: voronianski/esnextbin#12 (comment)
I don't know if this is an 'issue' or just something to be aware of, but I've noticed that when @observer
is used with the react-dnd@dragSource
decorator (and potentially others) it is important that these are applied in the correct order:
@dragSource(...)
@observer
class MyComponent {
}
If these are the other way around, then some of the optimizations that @observer
gives us don't appear to be applied (in particular, the shouldComponentUpdate
method doesn't appear to be used).
I haven't look into this much further than confirming that the order matters in my particular situation, and the above appears to be the 'right' way, but I can look into it more if it comes as a surprise.
I'm excited about MobX. I was able to reduce the code size significantly by switching from Redux to MobX. Thanks for the great library!
One of the things I usually need in my components is to do some logic whenever a certain prop has changed. Previously, I used to do that logic in componentDidMount
and in componentWillReceiveProps
. But now I have another source of data: MobX store. Changes happening to mobx data don't trigger componentWillReceiveProps
but instead componentWillReact
. So now I need to do that logic in componentWillReact
as well.
To reduce the number of lifecycle methods I use, I thought of doing:
componentDidMount() {
autorun(() => this.thatLogic());
}
Is the above equivalent to:
componentDidMount() {
this.thatLogic();
}
componentWillReact() {
this.thatLogic();
}
?
My guess is that the autorun solution is even better because it re-triggers the logic based on changes in the data that the logic cares about. As opposed to the latter which re-triggers the logic based on changes in data that the render cares about. Am I correct?
I am doing my first projecdt with Mobservable and I realized that data returned from the store is an observable.
I would like to have the store transform the data to a JSON object/array so that I don't have to call .toJSON()
everytime.
EDIT: nevermind, my mistake using key badly. Can be closed
Hi. I'm getting the warning below in the console log.
My app has a table with a detail view next to it. The detail view shows the selected table row. The table also highlights the row under the mouse. As notifications arrive from the server, some items will disappear from the table. All this works fine, except for the warning.
I think it happens when the mouse is over the selected table row and the selected item disappears as instructed by the server. It looks as though it's trying to then update the detail view, which has disappeared. I'm using react and mobservable in the obvious way, so I'm wondering whether this should be handled differently by mobservable somehow?
warning.js:45 Warning: forceUpdate(...): Can only update a mounted or mounting component. This usually means you called forceUpdate() on an unmounted component. This is a no-op. Please check the code for the undefined component.
warning @ warning.js:45
getInternalInstanceReadyForUpdate @ ReactUpdateQueue.js:34
ReactUpdateQueue.enqueueForceUpdate @ ReactUpdateQueue.js:135
ReactComponent.forceUpdate @ ReactComponent.js:86
reactiveRender @ index.js:60
ObservableView.compute @ observableview.js:57
(anonymous function) @ dnode.js:198
withStrict @ core.js:301
ViewNode.computeNextState @ dnode.js:197
ViewNode.notifyStateChange @ dnode.js:182
DataNode.notifyObservers @ dnode.js:113
DataNode.markReady @ dnode.js:107
ObservableValue.set @ observablevalue.js:34
ObservableObject.defineReactiveProperty.Object.defineProperty.set @ observableobject.js:61
ListVModelMasterObservable.setHighlightedId @ listViewModelObservable.ts:182
TableRow.render.React.createElement.onMouseEnter @ choresByReact.tsx:121
ReactErrorUtils.invokeGuardedCallback @ ReactErrorUtils.js:71
executeDispatch @ EventPluginUtils.js:79
executeDispatchesInOrder @ EventPluginUtils.js:102
executeDispatchesAndRelease @ EventPluginHub.js:43
executeDispatchesAndReleaseTopLevel @ EventPluginHub.js:54
forEachAccumulated @ forEachAccumulated.js:23
EventPluginHub.processEventQueue @ EventPluginHub.js:259
runEventQueueInBatch @ ReactEventEmitterMixin.js:18
ReactEventEmitterMixin.handleTopLevel @ ReactEventEmitterMixin.js:34
handleTopLevelWithoutPath @ ReactEventListener.js:93
handleTopLevelImpl @ ReactEventListener.js:73
Mixin.perform @ Transaction.js:136
ReactDefaultBatchingStrategy.batchedUpdates @ ReactDefaultBatchingStrategy.js:62
batchedUpdates @ ReactUpdates.js:94
ReactEventListener.dispatchEvent @ ReactEventListener.js:204
I use a custom react renderer that is not targeting the DOM thus I don't have the react-dom
package in my project at all. This ends up giving me this error:
ERROR in ./~/mobx-react/index.js
Module not found: Error: Cannot resolve module 'react-dom' in D:\Code\symphony\node_modules\mobx-react
@ ./~/mobx-react/index.js 198:8-71
Looking at mobx-react/index.js
it doesn't look like react-dom
is critical. How about making the package optional and permanently disabling your developer tools unless you have react-dom available?
A future improvement could be to support devtools with an arbitrary react renderer.
Thank you for the Boilerplate projects. But they are for JSX or the web.
Would you provide a React-native project using mobservable-react or point me to a demo please?
I took the sample code from the documentation here and modified it a bit to use an object
instead of an array
for the todos
field:
var todoStore = observable({
todos: { },
identity: function() { return this.todos; },
derived: function() {
let counter = 0; for(let key in this.todos) { ++counter; } return counter;
}
});
const print = function() {
console.log(
JSON.stringify(todoStore.todos),
JSON.stringify(todoStore.identity),
todoStore.derived
);
};
autorun(print);
// -> prints: { } { } 0
todoStore.todos.test = { title: "Take a walk", completed: false };
// -> should print: {"test":{"title":"Take a walk","completed":false}}
// {"test":{"title":"Take a walk","completed":false}} 1
todoStore.todos.test.completed = true;
// -> should print: {"test":{"title":"Take a walk","completed":true}}
// {"test":{"title":"Take a walk","completed":true}} 1
Now the whole automatic tracking doesn't work anymore. The places marked with -> should print: ...
do not print.
It I add another print();
call at the end of the sample code it prints this:
{"test":{"title":"Take a walk","completed":true}}
{"test":{"title":"Take a walk","completed":true}} 0
Note the wrong 0
at the end, since this should be 1
!
Do I miss something here? Do I need to use mobservable
for object
s differently from how I would use it intuitively?
React and all components that belong to it are no longer provided by React Native (0.26) and it's deprecated in 0.25.X.
import React from 'react-native' -> import React from 'react'
Any plan to update mobservable-react to react 0.14?
I refer to:
// TODO: Fix in 0.14: React.findDOMNode is deprecated. Please use ReactDOM.findDOMNode from require('react-dom') instead.
var node = React.findDOMNode(this);
Thank you
When using Typescript 1.6.2 with mobservable-react, I simply do this:
import {observer} from 'mobservable-react';
However, index.d.ts doesn't have a declaration of React available. So it would give this error:
(4,36): error TS2503: Cannot find namespace 'React'.
Perhaps you have a way to use TS with mobservable-react that isn't apparent?
index.js
current patch function:
function patch(target, funcName) {
var base = target[funcName];
var mixinFunc = reactiveMixin[funcName];
if (!base) {
target[funcName] = mixinFunc;
} else {
target[funcName] = function() {
base.apply(this, arguments); // (1)
mixinFunc.apply(this, arguments); // (2)
}
}
}
if (1) or (2) throw exception then other observer component will no effect never!
I fix this issues when surround try catch:
function patch(target, funcName) {
var base = target[funcName];
var mixinFunc = reactiveMixin[funcName];
if (!base) {
target[funcName] = mixinFunc;
} else {
target[funcName] = function() {
try {
base.apply(this, arguments);
mixinFunc.apply(this, arguments);
}
catch (err) {
console.error('mobx-react error ', err);
}
}
}
}
another same issues in
var reactiveMixin = {
componentWillMount: function() {
......
function initialRender() {
reaction = new mobx.Reaction(name, function() {
if (!isRenderingPending) {
isRenderingPending = true;
// should surround try catch
if (typeof self.componentWillReact === "function")
self.componentWillReact();
React.Component.prototype.forceUpdate.call(self)
}
});
}
}
Hey,
Currently using mobx-react is not possible with react 15.
Could you either please update the peerDependencies or (better) merge #33?
Please note that mobservable-react must be included after react.
Trying to work mobx-react into one of my projects but I'm having issues getting it to play nicely with Rollup.
Error: Module c:\Users\cheath\Desktop\front-end\node_modules\mobx-react\index.js does not export observer
Trying to import the lib and decorate a class:
....
import {observer} from "mobx-react";
@observer
class Header extends React.Component {
....
I can seemingly make it work by making the following mods:
//mobx-react/index.js
// Move the export order around. This project uses Dojo and if the AMD define is before the common we get some issues..
// UMD
if (typeof exports === 'object') {
module.exports = mrFactory(require('mobx'), require('react'), require('react-dom'));
} else if (typeof define === 'function' && define.amd) {
define('mobx-react', ['mobx', 'react', 'react-dom'], mrFactory);
} else {
this.mobxReact = mrFactory(this['mobx'], this['React'], this['ReactDOM']);
}
And changing my usage in my component to:
....
import * as MobX from "mobx-react";
@MobX.default.observer
class Header extends React.Component {
....
Obviously this is a bit of a clunky fix and I am hoping for a better solution.
My Rollup config:
gulp.task("bundle", function () {
return rollup.rollup({
entry: "./src/main.js",
plugins: [
nodeResolve({
main: true,
skip: [ "esri" ] // Let Dojo handle Esri
}),
commonjs(),
rollupPostcss({
plugins: [
require("autoprefixer"),
require("precss")
]
}),
replace({
"process.env.NODE_ENV": '"development"'
}),
babel({
exclude: "node_modules/**"
})
]
}).then(function(bundle) {
bundle.write({
format: "amd", // AMD is Dojo compatible
dest: "build/app/main.js",
sourceMap: true
});
})
});
check if inTransaction, if so, still force rendering?
There is a known issue facebook/react#2517 when shouldComponentUpdate prevents rerendering children components depended on the context. It would be nice to make this optimalization optional for compatiblitiy with non reactive libs e.g react-router and it's Link component (applying active link style depends on the context).
As I understand it, when setState is called in react, the component's render method is called and the component and its children will update the vDOM tree so that reactDOM.render can diff (only that section of the tree) and patch it to the real DOM...
With stateless components wrapped in observer, since they don't have a render method, will reactDOM diff the whole vDOM?
Is it ok to use nested components(routes) as observers, when using react-router or something else ? I decorated page with observer and got updates from store, it worked fine for me. Perhaps, there should be some sample or documentation notice about usage with react-router.
Are there any issues with doing something like this:
import React from 'react';
import { render } from 'react-dom';
import { observer } from 'react-redux';
import { store } from './store';
const App = observer(({ threads }) => (<div>{threads.map(thread => <div>{thread.name}</div>)}</div>);
render(<App {...store} />, document.getElementById('app'));
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.