Mobx computed doesn't work - javascript

I'm using mobx 4.2.0
When I try a use a computed property, I got some problem
Code like this:
class ODOM {
constructor(props) {
console.log('how many times')
}
#observable speed = 0
#action change(obj) {
console.log(obj)
Object.keys(obj).forEach(item => {
this[item] = obj[item]
})
}
#computed get velocity() {
console.log('entry')
return this.speed*60*60/1000
}
}
const model = new ODOM()
let total = 0
setInterval(() => {
model.change({
speed: ++total
})
}, 3000)
export default model
the console 'entry' only run once
What's the problem with those code

Your computed has to be observed in order for it to be re-computed when the observables it depends on changes.
This example uses an autorun to show the behavior:
class ODOM {
#observable speed = 0
#action change(obj) {
Object.keys(obj).forEach(item => {
this[item] = obj[item]
})
}
#computed get velocity() {
console.log('entry')
return this.speed*60*60/1000
}
}
const model = new ODOM()
let total = 0
setInterval(() => {
model.change({
speed: ++total
})
}, 1000);
autorun(() => {
console.log(model.velocity);
});

Computed re-calculation is only triggered by mobx in components that directly use this property in the render function and these react components must be annotated with #observer attribute.
otherwise use non computed/normal properties or cache the value yourself.

if #withRouter is under #observer will make #observer,#computed failure
like this (wrong):
#observer
// #ts-ignore
#withRouter
you need to write like this (correct):
// #ts-ignore
#withRouter
#observer

I found the problem.Cause the observer doesn't use correctly.

Related

How to subscribe on updates within ReactReduxContext.Consumer?

I would like to figure out how to subscribe on updates of a stored value it the redux store.
So far I've tried something like the following:
<ReactReduxContext.Consumer>
{({store}) => {
console.log('store:', store.getState());
const p = <p>{store.getState().value}</p>;
store.subscribe(() => {p.innerText = store.getState().value});
return p;
}}
</ReactReduxContext.Consumer>
bumping into the TypeError: can't define property "innerText": Object is not extensible error on updates.
So I wonder how to update the contents?
There are a few things about your code that are just not the way that we do things in React.
React is its own system for interacting with the DOM, so you should not attempt direct DOM manipulation through .innerText. Your code doesn't work because the variable p which you create is a React JSX Element rather than a raw HTML paragraph element, so it doesn't have properties like innerText.
Instead, you just return the correct JSX code based on props and state. The code will get updated any time that props or state change.
The ReactReduxContext is used internally by the react-redux package. Unless you have a good reason to use it in your app, I would not recommend it. There are two built-in ways that you can get a current value of state that is already subscribed to changes.
useSelector hook
(recommended)
export const MyComponent1 = () => {
const value = useSelector(state => state.value);
return <p>{value}</p>
}
connect higher-order component
(needed for class components which cannot use hooks)
class ClassComponent extends React.Component {
render() {
return <p>{this.props.value}</p>
}
}
const mapStateToProps = state => ({
value: state.value
});
const MyComponent2 = connect(mapStateToProps)(ClassComponent)
ReactReduxContext
(not recommended)
If anyone reading this has a good reason why they should need to use store.subscribe(), proper usage would look something like this:
const MyComponent3 = () => {
const { store } = useContext(ReactReduxContext);
const [state, setState] = useState(store.getState());
useEffect(() => {
let isMounted = true;
store.subscribe(() => {
if (isMounted) {
setState(store.getState());
}
});
// cleanup function to prevent calls to setState on an unmounted component
return () => {
isMounted = false;
};
}, [store]);
return <p>{state.value}</p>;
};
CodeSandbox Demo

React watch imported class property

I'm importing a plain class to my react (functional) component and want to be notified when an imported class property is set/updated. I've tried setting my imported class with just new, as a state variable with useState, as a ref with useRef - and have tried passing each one as a parameter to useEffect, but none of them are triggering the useEffect function when the property is updated a second time.
I've excluded all other code to drill down to the problem. I'm using Typescript, so my plain vanilla MyClass looks like this:
class MyClass {
userId: string
user: User?
constructor(userId: string){
this.userId = userId
// Do a network call to get the user
getUser().then(networkUser => {
// This works because I tried a callback here and can console.log the user
this.user = networkUser
}).catch(() => {})
}
}
And then in my component:
// React component
import { useEffect } from 'react'
import MyClass from './MyClass'
export default () => {
const myClass = new MyClass(userId)
console.log(myClass.user) // undefined here
useEffect(() => {
console.log(myClass.user) // undefined here and never called again after myClass.user is updated
}, [myClass.user])
return null
}
Again, this is greatly simplified. But the problem is that React is not re-rendering my component when the instance user object is updated from undefined to a User. This is all client side. How do I watch myClass.user in a way to trigger a re-render when it finally updates?
Let me guess you want to handle the business logic side of the app with OOP then relay the state back to functional React component to display.
You need a mechanism to notify React about the change. And the only way for React to be aware of a (view) state change is via a call to setState() somewhere.
The myth goes that React can react to props change, context change, state change. Fact is, props and context changes are just state change at a higher level.
Without further ado, I propose this solution, define a useWatch custom hook:
function useWatch(target, keys) {
const [__, updateChangeId] = useState(0)
// useMemo to prevent unnecessary calls
return useMemo(
() => {
const descriptor = keys.reduce((acc, key) => {
const internalKey = `##__${key}__`
acc[key] = {
enumerable: true,
configurable: true,
get() {
return target[internalKey]
},
set(value) {
if (target[internalKey] !== value) {
target[internalKey] = value
updateChangeId(id => id + 1) // <-- notify React about the change,
// the value's not important
}
}
}
return acc
}, {})
return Object.defineProperties(target, descriptor)
},
[target, ...keys]
)
}
Usage:
// React component
import { useEffect } from 'react'
import { useWatch } from './customHooks'
import MyClass from './MyClass'
export default () => {
const myClass = useMemo(() => new MyClass(userId), [userId])
useWatch(myClass, ['user'])
useEffect(() => {
console.log(myClass.user)
}, [myClass, myClass.user])
return null
}
Side Note
Not related to the question per se, but there're a few words I want to add about that myth I mentioned. I said:
props and context changes are just state change at a higher level
Examples:
props change:
function Mom() {
const [value, setValue] = useState(0)
setTimeout(() => setValue(v => v+1), 1000)
return <Kid value={value} />
}
function Dad() {
let value = 0
setTimeout(() => value++, 1000)
return <Kid value={value} />
}
function Kid(props) {
return `value: ${props.value}`
}
context change:
const Context = React.createContext(0)
function Mom() {
const [value, setValue] = useState(0)
setTimeout(() => setValue(v => v+1), 1000)
return (<Context.Provider value={value}>
<Kid />
</Context.Provider>)
}
function Dad() {
let value = 0
setTimeout(() => value++, 1000)
return (<Context.Provider value={value}>
<Kid />
</Context.Provider>)
}
function Kid() {
const value = React.useContext(Context)
return `value: ${value}`
}
In both examples, only <Mom /> can get <Kid /> to react to changes.
You can pass this.user as props and use props,.user in useEffeect. You could do that from the place getUser called.
A wholesome solution would be using a centralized state solution like redux or context API. Then you need to update store in getUser function and listen globalstate.user.
Conclusion
You need to pass this.user to the component one way or another. You need to choose according to the project.

Storing non-state variables in functional components

Below are two React Components that do almost the same thing. One is a function; the other is a class. Each Component has an Animated.Value with an async listener that updates _foo on change. I need to be able to access _foo in the functional component like I do with this._foo in the classical component.
FunctionalBar should not have _foo in the global scope in case there are more than one FunctionalBar.
FunctionalBar cannot have _foo in the function scope because _foo is reinitialized every time the FunctionalBar renders. _foo also should not be in state because the component does not need to render when _foo changes.
ClassBar does not have this problem because it keeps _foo initialized on this throughout the entire life of the Component.
How do I keep _foo initialized throughout the life of FunctionalBar without putting it in the global scope?
Functional Implementation
import React from 'react';
import { Animated, View } from 'react-native';
var _foo = 0;
function FunctionalBar(props) {
const foo = new Animated.Value(0);
_onChangeFoo({ value }) {
_foo = value;
}
function showFoo() {
let anim = Animated.timing(foo, { toValue: 1, duration: 1000, useNativeDriver: true });
anim.start(() => console.log(_foo));
}
useEffect(() => {
foo.addListener(_onChangeFoo);
showFoo();
return () => foo.removeListener(_onChangeFoo);
});
return <View />;
}
Classical Implementation
import React from 'react';
import { Animated, View } from 'react-native';
class ClassBar extends React.Component {
constructor(props) {
super(props);
this.state = { foo: new Animated.Value(0) };
this._foo = 0;
this._onChangeFoo = this._onChangeFoo.bind(this);
}
componentDidMount() {
this.state.foo.addListener(this._onChangeFoo);
this.showFoo();
}
componentWillUnmount() {
this.state.foo.removeListener(this._onChangeFoo);
}
showFoo() {
let anim = Animated.timing(this.state.foo, { toValue: 1, duration: 1000, useNativeDriver: true });
anim.start(() => console.log(this._foo));
}
_onChangeFoo({ value }) {
this._foo = value;
}
render() {
return <View />;
}
}
The useRef hook is not just for DOM refs, but can store any mutable value you like.
Example
function FunctionalBar(props) {
const [foo] = useState(new Animated.Value(0));
const _foo = useRef(0);
function showFoo() {
let anim = Animated.timing(foo, { toValue: 1, duration: 1000, useNativeDriver: true });
anim.start(() => console.log(_foo.current));
}
useEffect(() => {
function _onChangeFoo({ value }) {
_foo.current = value;
}
foo.addListener(_onChangeFoo);
showFoo();
return () => foo.removeListener(_onChangeFoo);
}, []);
return <View />;
}
You can use useRef hook (it's the recommended way stated in docs):
Declaring variable: const a = useRef(5) // 5 is initial value
getting the value: a.current
setting the value: a.current = my_value
Just to support Tholle answer here is the official documentation
Reference
However, useRef() is useful for more than the ref attribute. It’s
handy for keeping any mutable value around similar to how you’d use
instance fields in classes.
This works because useRef() creates a plain JavaScript object. The
only difference between useRef() and creating a {current: ...} object
yourself is that useRef will give you the same ref object on every
render.
Keep in mind that useRef doesn’t notify you when its content changes.
Mutating the .current property doesn’t cause a re-render. If you want
to run some code when React attaches or detaches a ref to a DOM node,
you may want to use a callback ref instead.
I've had some luck using the useRef hook with destructuring (+ an optional variable alias "my"), and then you keep all your values in the my object so you don't have to use multiple refs or keep using myref.current all the time:
function MyComponent(props) {
const componentRef = useRef({});
const { current: my } = componentRef;
my.count = 42;
console.log(my.count); // 42
my.greet = "hello";
console.log(my.greet); // hello
return <div />;
}
This is a pretty unusual example, but if I'm reading this correctly, you simply want to store unique _foo objects everytime the component mounts and destroy them when it unmounts, but also prevent extra rerenders when this value changes.
I have run into this scenario before and simple object (map / hash) should do the trick:
let foos = {}
let fooCount = 0
function F(props) {
useEffect(() => {
let fooId = fooCount++
foos[fooId] = new Animated.Value(0)
foos[fooId].addListener(...)
return () => foos[fooId].removeListener(...)
}, []) // <-- do not rerun when called again (only when unmounted)
...render...
}
or something to that effect. if you have a runnable example could tweak it to make it fit your example better. either way, most things with scope problems are solved with primitives.

Is there a way to check if the react component is unmounted?

I have a usecase where i need to unmount my react component. But in some cases, the particular react component is unmounted by a different function.
Hence, I need to check if the component is mounted before unmounting it.
Since isMounted() is being officially deprecated, you can do this in your component:
componentDidMount() {
this._ismounted = true;
}
componentWillUnmount() {
this._ismounted = false;
}
This pattern of maintaining your own state variable is detailed in the ReactJS documentation: isMounted is an Antipattern.
I'll be recommended you to use the useRef hook for keeping track of component is mounted or not because whenever you update the state then react will re-render the whole component and also it will trigger the execution of useEffect or other hooks.
function MyComponent(props: Props) {
const isMounted = useRef(false)
useEffect(() => {
isMounted.current = true;
return () => { isMounted.current = false }
}, []);
return (...);
}
export default MyComponent;
and you check if the component is mounted with if (isMounted.current) ...
I think that Shubham answer is a workaround suggested by react for people that need to transition their code to stop using the isMounted anti-pattern.
This is not necessarily bad, but It's worth listing the real solutions to this problem.
The article linked by Shubham offers 2 suggestions to avoid this anti pattern. The one you need depends on why you are calling setState when the component is unmounted.
if you are using a Flux store in your component, you must unsubscribe in componentWillUnmount
class MyComponent extends React.Component {
componentDidMount() {
mydatastore.subscribe(this);
}
render() {
...
}
componentWillUnmount() {
mydatastore.unsubscribe(this);
}
}
If you use ES6 promises, you may need to wrap your promise in order to make it cancelable.
const cancelablePromise = makeCancelable(
new Promise(r => component.setState({...}}))
);
cancelablePromise
.promise
.then(() => console.log('resolved'))
.catch((reason) => console.log('isCanceled', reason.isCanceled));
cancelablePromise.cancel(); // Cancel the promise
Read more about makeCancelable in the linked article.
In conclusion, do not try to patch this issue by setting variables and checking if the component is mounted, go to the root of the problem. Please comment with other common cases if you can come up with any.
Another solution would be using Refs . If you are using React 16.3+, make a ref to your top level item in the render function.
Then simply check if ref.current is null or not.
Example:
class MyClass extends React.Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
}
checkIfMounted() {
return this.elementRef.current != null;
}
render() {
return (
<div ref={this.elementRef} />
);
}
}
Using #DerekSoike answer, however in my case using useState to control the mounted state didn't work since the state resurrected when it didn't have to
What worked for me was using a single variable
myFunct was called in a setTimeout, and my guess is that when the same component initialized the hook in another page it resurrected the state causing the memory leak to appear again
So this didn't work for me
const [isMounted, setIsMounted] = useState(false)
useEffect(() => {
setIsMounted(true)
return () => setIsMounted(false)
}, [])
const myFunct = () => {
console.log(isMounted) // not always false
if (!isMounted) return
// change a state
}
And this did work for me
let stillMounted = { value: false }
useEffect(() => {
stillMounted.value = true
return () => (stillMounted.value = false)
}, [])
const myFunct = () => {
if (!stillMounted.value) return
// change a state
}
I got here because I was looking for a way to stop polling the API.
The react docs does cover the websocket case, but not the polling one.
The way I worked around it
// React component
React.createClass({
poll () {
if (this.unmounted) {
return
}
// otherwise, call the api
}
componentWillUnmount () {
this.unmounted = true
}
})
it works. Hope it helps
Please, let me know if you guys know any failing test case for this =]
If you're using hooks:
function MyComponent(props: Props) {
const [isMounted, setIsMounted] = useState<boolean>(false);
useEffect(() => {
setIsMounted(true);
}, []);
useEffect(() => {
return () => {
setIsMounted(false);
}
}, []);
return (...);
}
export default MyComponent;
The same idea but enother implementation
/**
* component with async action within
*
* #public
*/
class MyComponent extends Component {
constructor ( ...args ) {
// do not forget about super =)
super(...args);
// NOTE correct store "setState"
let originSetState = this.setState.bind(this);
// NOTE override
this.setState = ( ...args ) => !this.isUnmounted&&originSetState(...args);
}
/**
* no necessary setup flag on component mount
* #public
*/
componentWillUnmount() {
// NOTE setup flag
this.isUnmounted = true;
}
/**
*
* #public
*/
myCustomAsyncAction () {
// ... code
this.setState({any: 'data'}); // do not care about component status
// ... code
}
render () { /* ... */ }
}
I have solve with hot reload and react to different it events ✅
const {checkIsMounted} = useIsMounted(); //hook from above
useEffect(() => {
//here run code
return () => {
//hot reload fix
setTimeout(() => {
if (!checkIsMounted()) {
//here we do unmount action
}
}, 100);
};
}, []);
Pproblem
There is a problem when using the useState() hook. If you are also trying to do something else in a useEffect function (like fetching some data when the component is mounted) at the same time with setting the new value for the hook,
const [isMounted, setIsMounted] = useState(false)
useEffect(() =>
{
setIsMounted(true) //should be true
const value = await fetch(...)
if (isMounted) //false still
{
setValue(value)
}
return () =>
{
setIsMounted(false)
}
}, [])
the value of the hook will remain the same as the initial value (false), even if you have changed it in the beggining. It will remain unchanged for that first render, a new re-render being required for the new value to be applied.
For some reason #GWorking solution did not work too. The gap appears to happen while fetching, so when data arrives the component is already unmounted.
Solution
You can just combine both and and check if the component is unmounted during any re-render and just use a separate variable that will keep track to see if the component is still mounted during that render time period
const [isMounted, setIsMounted] = useState(false)
let stillMounted = { value: false }
useEffect(() =>
{
setIsMounted(true)
stillMounted.value = true
const value = await fetch(...)
if (isMounted || stillMounted.value) //isMounted or stillMounted
{
setValue(value)
}
return () =>
{
(stillMounted.value = false)
setIsMounted(false)
}
}, [isMounted]) //you need to also include Mounted values
Hope that helps someone!
There's a simple way to avoid warning
Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
You can redefine setState method inside your class component using this pattern:
componentWillUnmount() {
this._unmounted = true;
}
setState(params, callback) {
this._unmounted || super.setState(params, callback);
}
i found that the component will be unmounted, generate fill this var
if(!this._calledComponentWillUnmount)this.setState({vars});
You can use:
myComponent.updater.isMounted(myComponent)
"myComponent" is instance of your react component.
this will return 'true' if component is mounted and 'false' if its not..
This is not supported way to do it. you better unsubscribe any async/events
on componentWillUnmount.

How to bind React state to RxJS observable stream?

can someone help me how to bind React State to RxJS Observable? I did sth like
componentDidMount() {
let source = Rx.Observable.of(this.state.val)
}
The ideal result is, whenever this.state.val updated (via this.setState(...)) source get updated too, so I can combine source with other RxJS observable stream.
However, in this case, source only get updated once, even after this.state.val is updated and component is re-rendered.
// Ideal result:
this.state.val = 1
source.subscribe(val => console.log(x)) //=> 1
this.state.val = 2
source.subscribe(val => console.log(val)) //=> 2
// Real result:
this.state.val = 1
source.subscribe(val => console.log(x)) //=> 1
this.state.val = 2
source.subscribe(val => console.log(val)) //=> 1 ???WTH
It might be because componentDidMount() only invoked once in React lifetime. so I move source to componentDidUpdate() which is invoked everytime after component is rendered. However, the result still remain the same.
So the question is how to make source updated whenever this.state.val updated?
Updated: Here is the solution I used to solve the prob, using Rx.Subject
// Component file
constructor() {
super(props)
this.source = new Rx.Subject()
_onChangeHandler(e) {
this.source.onNext(e.target.value)
}
componentDidMount() {
this.source.subscribe(x => console.log(x)) // x is updated
}
render() {
<input type='text' onChange={this._onChangeHandler} />
}
//
Update
To abstract out some of the below complexity, use recompose's mapPropsStream or componentFromStream. E.g.
const WithMouseMove = mapPropsStream((props$) => {
const { handler: mouseMove, stream: mouseMove$ } = createEventHandler();
const mousePosition$ = mouseMove$
.startWith({ x: 0, y: 0 })
.throttleTime(200)
.map(e => ({ x: e.clientX, y: e.clientY }));
return props$
.map(props => ({ ...props, mouseMove }))
.combineLatest(mousePosition$, (props, mousePosition) => ({ ...props, ...mousePosition }));
});
const DumbComponent = ({ x, y, mouseMove }) => (
<div
onMouseMove={mouseMove}
>
<span>{x}, {y}</span>
</div>
);
const DumbComponentWithMouseMove = WithMouseMove(DumbComponent);
Original Post
For a slightly updated answer to the OP's updated answer, using rxjs5, I came up with the following:
class SomeComponent extends React.Component {
constructor(props) {
super(props);
this.mouseMove$ = new Rx.Subject();
this.mouseMove$.next = this.mouseMove$.next.bind(this.mouseMove$);
this.mouseMove$
.throttleTime(1000)
.subscribe(idx => {
console.log('throttled mouse move');
});
}
componentWillUnmount() {
this.mouseMove$.unsubscribe();
}
render() {
return (
<div
onMouseMove={this.mouseMove$.next}
/>
);
}
}
Some notable additions:
onNext() is now next()
binding the observable next method allows it to be passed directly to the mouseMove handler
streams should be unsubscribed in componentWillUnmount hook
Furthermore, the subject streams initialized in the component constructor hook can be passed as properties to 1+ child component(s), which could all push to the stream using any of the observable next/error/complete methods. Here's a jsbin example I put together demonstrating multiple event streams shared between multiple components.
Curious if anyone has ideas on how to better encapsulate this logic to simplify stuff like binding and unsubscribing.
One option could be to use Rx.Observable.ofObjectChanges > cf. https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/operators/ofobjectchanges.md.
However :
It uses Object.observe which is not a standard feature, hence will have to be polyfilled in some browsers and is actually being removed from inclusion in ecmascript (cf. http://www.infoq.com/news/2015/11/object-observe-withdrawn). Not the choice for the future, but it is easy to use, so if it is just for your own needs, why not.
Other option is to use a subject in one of the three methods at your disposal according to your use case : shouldComponentUpdate, componentWillUpdate, componentDidUpdate. Cf. https://facebook.github.io/react/docs/component-specs.html for when each function is executed. In one of these methods, you would check if this.state.val has changed, and emits its new value on the subject if it did.
I am not a reactjs specialist, so I guess they might be other options.
Although a subject will work, I think the best practice is to avoid using a subject when you can use an observable. In this case you can use Observable.fromEvent:
class MouseOverComponent extends React.Component {
componentDidMount() {
this.mouseMove$ = Rx.Observable
.fromEvent(this.mouseDiv, "mousemove")
.throttleTime(1000)
.subscribe(() => console.log("throttled mouse move"));
}
componentWillUnmount() {
this.mouseMove$.unsubscribe();
}
render() {
return (
<div ref={(ref) => this.mouseDiv = ref}>
Move the mouse...
</div>
);
}
}
ReactDOM.render(<MouseOverComponent />, document.getElementById('app'));
Here it is on codepen....
It seems to me that there are other times when a Subject like the best choice, like when a custom React component executes a function when an event occurs.
I would highly recommend reading this blog post on streaming props to a React component using RxJS:
https://medium.com/#fahad19/using-rxjs-with-react-js-part-2-streaming-props-to-component-c7792bc1f40f
It uses FrintJS, and applies the observe higher-order component for returning the props as a stream:
import React from 'react';
import { Observable } from 'rxjs';
import { observe } from 'frint-react';
function MyComponent(props) {
return <p>Interval: {props.interval}</p>;
}
export default observe(function () {
// return an Observable emitting a props-compatible object here
return Observable.interval(1000)
.map(x => ({ interval: x }));
})(MyComponent);
You can do it using hooks.
Here is a code sample
import { Observable, Subscription } from 'rxjs';
import { useState, useEffect } from 'react';
export default function useObservable<T = number | undefined>(
observable: Observable<T | undefined>,
initialState?: T): T | undefined {
const [state, setState] = useState<T | undefined>(initialState);
useEffect(() => {
const subscription: Subscription = observable.subscribe(
(next: T | undefined) => {
setState(next);
},
error => console.log(error),
() => setState(undefined));
return () => subscription.unsubscribe();
}, [observable])
return state;
}

Categories

Resources