Mobx-react no render but state changed - javascript

I decided to try Mobx and faced the problem of the component's lack of response to a changing field in the repository. I looked at similar topics, but I still don't understand what the problem is. If you print the property value to the console after the change, you see the actual result.
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from "mobx-react";
import AppStore from "./AppStore";
import App from './App';
ReactDOM.render(
<React.StrictMode>
<Provider AppStore={AppStore}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
App.js
import React, { Component } from "react";
import { inject, observer } from "mobx-react";
#inject('AppStore')
#observer class App extends Component {
render() {
const { AppStore } = this.props;
console.log(AppStore);
return(
<div className="App">
{ this.props.AppStore.counter }
<hr/>
<button onClick={this.props.AppStore.increment}>+</button>
<button onClick={this.props.AppStore.decrement}>-</button>
</div>
)
}
}
export default App;
AppStore.js
import { observable, action } from "mobx";
class AppStore {
#observable counter = 0;
#action increment = () => {
this.counter = this.counter + 1;
console.log(this.counter);
}
#action decrement = () => {
this.counter = this.counter - 1;
console.log(this.counter);
}
}
const store = new AppStore();
export default store;

Since mobx#6.0.0 decorators are not enough. You have to make your class observable manually with makeObservable as well.
class AppStore {
#observable counter = 0;
constructor() {
makeObservable(this);
}
#action increment = () => {
this.counter = this.counter + 1;
}
#action decrement = () => {
this.counter = this.counter - 1;
}
}

Related

Context empty after async initialisation

I am trying to fetch data from a backend API and initialise my FieldsContext. I am unable to do it, it returns an empty fields array in the Subfields component. I have spent hours on fixing it. But I eventually give up. Please take a look into this. Thanks in advance.
Here is my code
App.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css'
import './App.css';
import Index from './components/pages/index/'
import FieldsProvider from './providers/fieldProvider'
import AuthProvider from './providers/authProvider'
import {BrowserRouter as Router,Switch,Route} from 'react-router-dom';
import SubFields from './components/pages/subfields';
function App() {
return (
<Router>
<AuthProvider>
<FieldsProvider>
<Switch>
<Route exact path="/" component={Index} />
<Route exact path="/:fieldid/subfields" component={SubFields} />
</Switch>
</FieldsProvider>
</AuthProvider>
</Router>
);
}
export default App;
FieldsContext.js
import React from 'react'
const FieldsContext = React.createContext();
export default FieldsContext
FieldsProvider.js
import React, { Component } from 'react'
import FieldsContext from '../libs/fieldContext'
export default class FieldsProvider extends Component {
state = {fields:[]}
getFields()
{
fetch('/api/fields')
.then(res => res.json())
.then(fields => this.setState({fields}));
}
async componentDidMount() {
await this.getFields();
}
render() {
return (
<FieldsContext.Provider value={this.state} >
{this.props.children}
</FieldsContext.Provider>
)
}
}
Subfields.js
import React, { Component } from 'react'
import FieldsContext from '../../../libs/fieldContext'
import FieldsList from '../../Fields/fieldlist'
export default class SubFields extends Component {
componentDidMount(){
// const fieldId = this.props.match.params.fieldid;
console.log(this.context);
}
render() {
return (
<div>
</div>
)
}
}
SubFields.contextType = FieldsContext
try using an ES6 Arrow function, which binds the function to the object instance, so that this refers to the object instance of the class when it is called.
When its called asynchronously, this will refer the the class object instance you want to update.
import React, { Component } from 'react'
import FieldsContext from '../libs/fieldContext'
export default class FieldsProvider extends Component {
state = {fields:[]}
// ES6 Arrow function
getFields = () =>
{
fetch('/api/fields')
.then(res => res.json())
.then(fields => this.setState({fields}));
}
async componentDidMount() {
await this.getFields();
}
render() {
return (
<FieldsContext.Provider value={this.state} >
{this.props.children}
</FieldsContext.Provider>
)
}
}
Alternatively, Try binding of your function in the class constructor.
export default class FieldsProvider extends Component {
state = {fields:[]}
constructor(props) {
//bind the class function to this instance
this.getFields = this.getFields.bind(this);
}
//Class function
getFields()
{
fetch('/api/fields')
.then(res => res.json())
.then(fields => this.setState({fields}));
}
async componentDidMount() {
await this.getFields();
}
render() {
return (
<FieldsContext.Provider value={this.state} >
{this.props.children}
</FieldsContext.Provider>
)
}
}
As a side note: Prefer to use functional components for consuming of ContextAPI.
import React, { Component } from 'react'
import FieldsContext from '../../../libs/fieldContext'
import FieldsList from '../../Fields/fieldlist'
export default function SubFields (props) {
const {
match
} = props;
//much better way to consume mulitple Contexts
const { fields } = React.useContext(FieldsContext);
//useEffect with fields dependency
React.useEffect(() => {
console.log(fields);
},[fields]);
return (
<div>
</div>
)
}

How to watch class object values inside `useEffect`

App.js
import React, { useRef, useEffect } from "react";
import Token from "./Token";
export default function App() {
const tokenizerRef = useRef(new Token());
useEffect(() => {
console.log("current token index: ", tokenizerRef.current.currentIndex);
}, [tokenizerRef.current.currentIndex]);
return <button onClick={tokenizerRef.current.advance}>Next</button>;
}
Token.js
class Token {
constructor() {
this.currentIndex = -1;
}
advance() {
this.currentIndex++;
}
}
export default Token;
I've a Token object ref inside App.js, and would like to watch the object field values(for this case when currentTokenIndex changes).
Currently clicking Next button, doesn't trigger the useEffect.
A better way of doing this, pointing to the right direction, will be appreciated.
We can use observer/subscriber pattern, specifically mobx for this purpose.
Token.js
import { makeAutoObservable } from "mobx";
class Token {
constructor() {
this.currentIndex = -1;
makeAutoObservable(this);
}
advance() {
this.currentIndex++;
}
}
export default Token;
App.js
import React, { useEffect } from "react";
import { observer } from "mobx-react";
const App = observer(({ tokenizer }) => {
useEffect(() => {
console.log('current index changed: ', tokenizer.currentIndex)
}, [tokenizer.currentIndex])
return (
<div>
<h1>{tokenizer.currentIndex}</h1>
<button onClick={() => tokenizer.advance()}>Next</button>
</div>
);
});
export default App;
Index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import Token from "./Token";
const tokenizer = new Token();
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App tokenizer={tokenizer} />
</React.StrictMode>,
rootElement
);

React: Why won't this React Component render?

I'm in the process of writing a desktop application in React using Electron and Meteor.js
I have the following React Component class:
import React from "react"
export class MemoryMap extends React.Component{
constructor(props){
super(props);
this.state = {
memory : [],
mem_max : 0xFFFF,
}
this.readByte = function(byte){
return this.state.memory[byte];
};
this.writeByte = function(mem_addr, byte){
if(mem_addr >= 0 && mem_addr < this.state.mem_max) {
this.state.memory[mem_addr] = byte;
}
};
for(let i = 0; i < 10000; i++){
this.state.memory[i] = 0x0000;
}
}
render(){
return(
<div>
<h1>{this.state.memory[0x0000]}</h1>
</div>
);
}
}
export const MemMap = new MemoryMap();
I attempt to render this class in Main.jsx as such:
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import {MemMap} from "./CPU_Elements/MemoryMap";
Meteor.startup(() => {
render(<MemMap/>, document.getElementById("react-target"));
Desktop.send("desktop", "init");
});
When called this way, the program crashes on this line. The Desktop.send function is never called.
If I re-write MemoryMap as such, where the render function becomes a class method:
import React from "react"
export class MemoryMap extends React.Component{
constructor(props){
super(props);
this.state = {
memory : [],
mem_max : 0xFFFF,
}
this.readByte = function(byte){
return this.state.memory[byte];
};
this.writeByte = function(mem_addr, byte){
if(mem_addr >= 0 && mem_addr < this.state.mem_max) {
this.state.memory[mem_addr] = byte;
}
};
for(let i = 0; i < 10000; i++){
this.state.memory[i] = 0x0000;
}
this.render = function(){
return(
<div>
<h1>{this.state.memory[0x0000]}</h1>
</div>
);
}
}
}
export const MemMap = new MemoryMap();
And the main.jsx file is re-written to call that method:
import React from 'react';
import { Meteor } from 'meteor/meteor';
import { render } from 'react-dom';
import {MemMap} from "./CPU_Elements/MemoryMap";
Meteor.startup(() => {
render(MemMap.render(), document.getElementById("react-target"));
Desktop.send("desktop", "init");
});
The element renders just fine.
Why is this? Why can't I use the HTML tag formatting, as shown in React's tutorials?
change this:
export const MemMap = new MemoryMap();
to:
export const MemMap = MemoryMap;
Since you should export the component defination, not creating an instance of it and exporting it. (that's why obj.render() works but <obj/> doesn't.)

Decorate imported React component with higher order component

I know how to decorate a component before exporting it like this:
export default ButtonDecorator(MainButton)
But if I try to make a index to import it and decorate it differently for some cases. It wont work.
Here is the example of the index:
import MainButton from './main/main_button'
import BackButton from './back/back_button'
import { ButtonDecorator, LinkDecorator } from 'decorators'
export {
ButtonDecorator(MainButton) as MainButton,
LinkDecorator(MainButton) as MainHrefButton,
BackButton
}
And the higher order component:
import React, { Component } from 'react'
let Btn = InnerComponent => {
class NewBtn extends Component {
constructor(props) {
super(props)
}
render() {
return (
<button onClick={this.props.onClick}>
<InnerComponent disabled={this.props.disabled} />
</button>
)
}
}
return NewBtn
}
export default Btn
What is the right way to do this?
As far as I know export requires a name (variable), and not a function invocation. Try this:
const ButtonDecoratedMainButton = ButtonDecorator(MainButton);
const LinkDecoratedMainButton = LinkDecorator(MainButton);
export {
DecoratedMainButton as MainButton,
LinkDecoratedMainButton as MainHrefButton,
BackButton
}

own timer component with react, reflux not working

I try to get used to reflux and forked a example repo. My full code is here [ https://github.com/svenhornberg/react-starterkit ]
I want to create a timer component which gets the current time from a timestore, but it is not working. The DevTools does not show any errors. This must be some newbie mistakes, but I do not find them.
Edit1: I added a line in home //edit1
Edit2: I think the mistake may be in componentDidMount in home.jsx
FIXED I need to trigger my time, see my answer.
Store
import Reflux from 'reflux';
import TimeActions from '../actions/timeActions';
var TimeStore = Reflux.createStore({
listenables: timeActions,
init() {
this.time = '';
},
onCurrenttime() {
this.time = '13:47';
}
});
export default TimeStore;
Actions
import Reflux from 'reflux';
var TimeActions = Reflux.createActions([
'currenttime'
]);
export default TimeActions;
Component
import React from 'react';
class Timer extends React.Component {
constructor(){
super();
}
render() {
var time = this.props.time;
return (
<div>
{ time }
</div>
);
}
}
Timer.propTypes = {
time : React.PropTypes.string
}
export default Timer;
I wanted to use the timer component in the home.jsx
import React from 'react';
import ItemList from '../components/itemList.jsx';
import ItemStore from '../stores/itemStore';
import ItemActions from '../actions/itemActions';
import Timer from '../components/timer.jsx';
import TimeStore from '../stores/timeStore';
import TimeActions from '../actions/timeActions';
class Home extends React.Component {
constructor(props){
super(props);
this.state = {
items : [],
loading: false,
time : '' //edit1
};
}
componentDidMount() {
this.unsubscribe = ItemStore.listen(this.onStatusChange.bind(this));
this.unsubscribe = TimeStore.listen(this.onStatusChange.bind(this));
ItemActions.loadItems();
TimeActions.currenttime();
}
componentWillUnmount() {
this.unsubscribe();
}
onStatusChange(state) {
this.setState(state);
}
render() {
return (
<div>
<h1>Home Area</h1>
<ItemList { ...this.state } />
<Timer { ...this.state } />
</div>
);
}
}
export default Home;
I fixed it thanks to: How to Make React.js component listen to a store in Reflux
I have to trigger my time:
var TimeStore = Reflux.createStore({
listenables: TimeActions,
init() {
this.time = '';
},
onCurrenttime() {
this.trigger({
time : '13:47'
});
}
});

Categories