react mobx, component does not render new observable - reactjs

I am using Mobx with React (typescript) to get some data from firebase and display it. at the first render no data is rendered on the screen till I change for instance tabs in view that the component get re-rendered.
the component class:
interface Props {id: string; mytore?: MyStore;}
#inject('mystore')
#observer
export class myClass extends Component<Props>{
constructor(props: Props) {super(props);}
componentDidMount() { this.props.mystore!.getBs(this.props.id);}
render() {
const { arrB } = this.props.mystore!;
return (
<div>{arrB.map((e) => (<h3 key={`${e.id}`}>{e.name}</h3>))}</div>
);
}
}
the store class
export class MyStore{
#observable arrA=[];
#observable arrB=[];
constructor(){this.loadAllAs()}
#action.bound loadAllAs(){runInAction(async()=>(this.arrA=await /*fetchfunction*/))}
#action getBs(id){this.arrB=arrA.filter(/*some logic*/)}
}
so here the arrB gets manipulated after calling the method, and it is an observable which get destructures at render method of the component so I was expecting any changes to the arrB result in a re-render but nothing happens till regarding other actions componentDidMount gets called.

Because arrB must be #computed
computed
#computed
getBs(id){
return this.arrA.filter(/*some logic*/)}
}
const { getBs } = this.props.mystore!;
getBs(id).map...

Related

React Respond to Property Object Change

I'm looking for a way to update a React.Component when the internal state of one of its properties (an object) changes.
Example:
export class ParentClass extends React.Component {
constructor(props) {
super(props);
this.someObject = new SomeObject();
}
...
render() {
return <ChildClass someObject=this.someObject />
}
}
When I call this.someObject.change() in the parent class. How can I signal to the child class that it should re-render since the internal state of someObject just changed?
Normally, if I updated a primitive value, I believe this would trigger a re-render of the child. However, since the object pointer hasn't changed, I don't believe the child class is aware a change has occurred.
Is it possible to trigger a re-render of ChildClass in this case? Is there maybe a better convention for managing this relationship?
This could have a performance impact, but I resolved this by moving someObject into the state of the parent and setting the Child property to {this.state.someObject}.
When someObject (in this case roadTileMatrix) changes, I clone the object, make the changes, then set the new object as the state. This seems to be a common pattern from my reading.
Alternatively, as a more lightweight solution, it looks like you could instead set a key value in the state that will change each time changes are made to someObject, triggering a re-render of the child.
Code here:
import React from 'react'
import PropTypes from 'prop-types';
import { RoadTileMatrix, Grid } from './Grid';
import { TravelGraph } from './TravelGraph';
export class RoadNetwork extends React.Component {
constructor(props) {
super(props);
const rows = this.props.rows;
const cols = this.props.cols;
this.state = {
roadTileMatrix: new RoadTileMatrix(rows, cols, null)
};
this.addTile = this.addTile.bind(this);
}
addTile(r, c) {
const rows = this.props.rows;
const cols = this.props.cols;
const oldArray = this.state.roadTileMatrix.innerArray;
const newRoadTileMatrix = new RoadTileMatrix(rows, cols, oldArray);
newRoadTileMatrix.addTile(r, c, true);
this.setState({
roadTileMatrix: newRoadTileMatrix,
});
}
render() {
return (
<div>
<Grid roadTileMatrix={this.state.roadTileMatrix} addTile={this.addTile} />
<TravelGraph />
</div>
);
};
}
RoadNetwork.propTypes = {
rows: PropTypes.number.isRequired,
cols: PropTypes.number.isRequired,
}

React Mobx - Change store from other store not render observer components

I have to update #observable variable by #action function on one store.
When I call this #action from component class it change the #observable variable and re-render the #observer components.
But when I try to Call this #action from #action in other store, by using callback, the #observable variable is changed but #observer components not re-render.
One Store:
class OneStore {
#observable variable;
#action
setVariable(value) {
this.variable = value;
}
}
Other Store:
class OtherStore {
#action
setVariable(callBack, value) {
callBack(value); //callBack is oneStore.setVariable
}
}
#observer Component:
#inject('oneStore')
#observer
class ObserverComponent extends Component {
render() {
if (this.props.oneStore.variable) {
return (
<div> {this.props.oneStore.variable} <div/>
);
} else { return null; }
}
}
I tried also to get the variable by #computed get function, it still not re-render.
I tried to add bound to the action - #action.bound and it simply works...
I think the this of OneStore was destroyed after nesting the callback.
The correct code is:
class OneStore {
#observable variable;
#action.bound
setVariable(value) {
this.variable = value;
}
}

react native: attempted to assign to readonly property

I create a React ref by calling React.createRef in React Native. Then I assign it to a ref. I got the error: Attempted to assign to readonly property
export default class List extends PureComponent<Props, object> {
private flatListRef: React.RefObject<FlatList<any>>;
constructor(props) {
super(props);
this.flatListRef = React.createRef();
}
render() {
return (
/.../
<FlatList ref={this.flatListRef}></FlatList>
)
}
}
But When I use the callback way to assign the react ref, everything is ok.
<FlatList ref={ele => { this.flatListRef = ele }}></FlatList>
I have no idea what's the difference between the two ways
the ref property in React is expecting a function and it's called immediately after the component is mounted. You can do other things besides setting a reference.
https://zhenyong.github.io/react/docs/more-about-refs.html#the-ref-callback-attribute

One React Component. Two HOCs. How to make setState correctly update props?

Given a standard compose function and a 'div' Component, how would you write the two HOCs such that:
The 'div' element starts as a 20px green box, then on click, becomes a 50px blue box.
The concerns of - a: merging state with props, and b: triggering a state change, are handled by separate HOCs.
the updater HOC maps state to props, and sets a default state
the dispatcher HOC accepts a function to get the new state on click
The example below works to get a green box, and correctly fires the handler. The update only happens in the state of the Dispatcher HOC's state. The updater HOC's state remains unchanged, as do its props.
I'm really curious to understand what's happening. Flipping the two HOCs' order in compose causes the handler not to be set. Since they both merge in {...this.props}, that doesn't make sense to me. Guessing there's something I don't understand about how multiple HOCs merge props and state.
const HOCDispatcher = myFunc => BaseComponent => {
return class Dispatcher extends React.Component {
constructor(props,context){
super(props,context);
this.handlerFn = (event)=>{this.setState(myFunc)}
}
render(){
return createElement(BaseComponent,{...this.props,onClick:this.handlerFn});
}
}
}
const HOCUpdater = defaultState => BaseComponent => {
return class Updater extends React.Component {
constructor(props,context){
super(props,context);
this.state = Object.assign({},defaultState,this.state);
}
render(){
return createElement(BaseComponent,{...this.props,...this.state});
}
}
}
const MyComponent = compose(
HOCDispatcher(()=>({
style:{width:'50px',height:'50px',background:'blue'}
})),
HOCUpdater({
style:{width:'20px',height:'20px',background:'green'}
}),
)('div');
If you try to simplify or compile your code in a way to a less complicated structure you can understand it better:
The initial version of MyComponent
const MyComponent= class Dispatcher extends React.Component {
constructor(props,context){
super(props,context);
this.handlerFn = (event)=>{this.setState({
style:{width:'50px',height:'50px',background:'blue'}
})}
}
render(){
return <HOCUpdater onClick={this.handlerFn}/>
}
}
Where HOCUpdater also renders as:
class Updater extends React.Component {
constructor(props,context){
super(props,context);
this.state = {
style:{width:'20px',height:'20px',background:'green'}
};
}
render(){
return <div style:{width:'20px',height:'20px',background:'green'}/>;
}
}
Thus rendering the green box.
After triggering the click
const MyComponent= class Dispatcher extends React.Component {
constructor(props,context){
super(props,context);
this.handlerFn = (event)=>{this.setState({
style:{width:'50px',height:'50px',background:'blue'}
})};
this.state= {
style:{width:'50px',height:'50px',background:'blue'}
};
}
render(){
return <HOCUpdater onClick={this.handlerFn}/>
}
}
If you pay attention to the render, it's still the same because this.props has not changed and it is still empty. Thus no change to the style of the box whereas the state of the Dispatcher is changed!
Did you see where you went wrong? Well, just change this.props to this.state in the Dispatcher and you'll see the magic happen.
But wait, there's more!
What happens if you have a line of code like this?
createElement('div',{
style:{width:'50px',height:'50px',background:'blue'},
style:{width:'20px',height:'20px',background:'green'}
});
Well, it still renders the first one (the blue box) but to avoid this try changing the render method of HOCUpdater to this:
return createElement(BaseComponent,{...this.state});
and also add a componentWillReceiveProps method, so your HOCUpdater will look like this:
const HOCUpdater = defaultState => BaseComponent => {
return class Updater extends React.Component {
constructor(props,context){
super(props,context);
this.state = Object.assign({},defaultState,this.state);
}
componentWillReceiveProps(nextProps){
this.setState(nextProps);
}
render(){
return createElement(BaseComponent,{...this.state});
}
}
}

How to make simple mobx reaction work in sub component

I miss something in mobx observables and reactions.
I prepared two examples, one of them works, the other does not, I don't understand why.
Example 1 (does not work):
#observer class Alert1 extends Component {
constructor(props) {
super(props);
this.r2 = reaction(
() => this.props.v1,
v => console.log("Alert1 reaction trigger",v)
);
}
render() {
return null;
}
}
#observer class Main extends Component {
#observable v1 = false;
render() {
return (
<div>
<Alert1 v1={this.v1} />
<button onClick={e=>this.v1=!this.v1}>Switch</button>
</div>
);
}
}
In example 1 we just send observable variable in props and create reaction in Alert1 component, but it does not trigger.
Example 2 (works):
#observer class Alert2 extends Component {
constructor(props) {
super(props);
this.r2 = reaction(
() => this.props.someObj.v1,
v => console.log("Alert2 reaction trigger",v)
);
}
render() {
return null;
}
}
#observer class Main extends Component {
constructor(props) {
this.someObj = observable({v1:observable(false)});
}
render() {
return (
<div>
<Alert2 someObj={this.someObj} />
<button onClick={e=>this.someObj.v1=!this.someObj.v1}>Switch</button>
</div>
);
}
}
That's almost the same as example 1, but we wrap v1 observable into the other observable. Alert2 reaction works.
The same time if we move reactions from Alert1 and Alert2 components to the Main component's constructor, both reactions works.
Here's jsfiddle example with both components, https://jsfiddle.net/kasheftin/zex0qjvf/1/
See https://mobxjs.github.io/mobx/best/react.html, in your first example, you are not passing an observable around, but just a plain boolean value (true or false), so there is nothing for the reaction to react to. In javascript, all values are immutable so per definition something that is observable. It are the properties that are observable.
In the second example you pass an object with an observable property, so that is something that can be reacted to.
Note that creating a boxed observable would also work, as those can be passed around as first class citizens. E.g.: v1 = observable(false) and reaction(() => this.props.v1.get(), ...

Resources