component not re-rendering when updating the state in mobx

Viewed 3524

I'm trying to understand why my app is not re rendering when I changing my state in the mobx observable names array. I'm changing the value with the input tag. would love for some help :)

observers component:

import {observable, action, autorun, computed} from 'mobx'

class TodosStore {
    @observable names = ["p1", "p2", "p3"]
    @observable filter = ""

    @action
    get filterredValue(){
        return store.names.filter(word => word.includes(this.filter))
    }
}

//@ts-ignore
var store = window.store = new TodosStore

export default store


autorun(() => {
    console.log(store.filter); 
    console.log(store.names); 
})

and this is my app component:

import React from 'react';
import './App.css';
import store from  './components/observers'

class App extends React.Component {
  constructor(props :any) {
    super(props);
    this.setName = this.setName.bind(this);
  }

  setName = (e : any) => {
    store.filter = e.target.value

  }

  render() {
  return (
    <div className="App">
      <header className="App-header">
        {store.filterredValue.map((name) => <li key={name}>{name}</li>)}
        <input
          onChange={(e) => this.setName(e)}
          />
      </header>
    </div>
  );
}
}

export default App;
4 Answers

First of all you need to wrap every component that uses observable state into observer decorator, like that:

import {observer} from 'mobx-react'

@observer
class App extends React.Component {
  // ...
}

// or if you are using functional components:

const App = observer(() => {
  // ...
})

Also, if you are using MobX version 6 you need to add makeObservable call inside class constructor:

import {observable, action, autorun, computed, makeObservable} from 'mobx'

class TodosStore {
    @observable names = ["p1", "p2", "p3"]
    @observable filter = ""

    constructor() {
       makeObservable(this);
    }

    @action
    get filterredValue(){
        return store.names.filter(word => word.includes(this.filter))
    }
}

More about MobX and React integration in the docs

You can define things like observable, computed and action in TodoStore using makeObservable as decorators are currently not being preferred (decorators are currently not an ES standard, and the process of standardization is taking a long time):

TodoStore:

import { observable, autorun, computed, makeObservable, action } from "mobx";

class TodoStore {
  names = ["p1", "p2", "p3"];
  filter = "";

  constructor() {
    makeObservable(this, {
      names: observable,
      filter: observable,
      filterredValue: computed,
      setFilter: action,
    });
    autorun(() => {
      console.log(this.filter);
      console.log(this.names);
    });
  }

  get filterredValue() {
    return this.names.filter((word) => word.includes(this.filter));
  }

  setFilter(filter) {
    this.filter = filter;
  }
}

export const todoStore = new TodoStore();

And, here is App component using observer (A higher order component which makes a functional or class based React component re-render when observables change):

App:

import { observer } from "mobx-react";
import { Component } from "react";

class App extends Component<any> {
  setName = (e: React.ChangeEvent<HTMLInputElement>) => {
    this.props.store.setFilter(e.target.value);
  };

  render() {
    return (
      <div className="App">
        <header className="App-header">
          {this.props.store.filterredValue.map((name) => (
            <li key={name}>{name}</li>
          ))}
          <input onChange={(e) => this.setName(e)} />
        </header>
      </div>
    );
  }
}

export default observer(App);

Demo:

Edit my-mobx-react

Add shouldComponentUpdate to your component. Make it return true when the state is changed.

You should set name and value to your input.

   <input name="" value="" onChange={(e) => this.setName(e)} />
Related