I am attempting to write a unit test for mobx-based reaction. For some reason, when the @observable
value is updated in an @action
, the @puted
function does not re-run as you'd expect.
Code:
STORE
class NameStore {
@observable name;
@action setName(name) {
this.name = name;
}
}
COMPONENT 1
@observer
class Name {
@puted get name() {
if (this.props.nameStore.name) {
return `${this.props.nameStore.name} is awesome!`;
}
return null;
}
render() {
return (
<div className="name">
{this.name}
</div>
);
}
}
COMPONENT 2
@observer
class Name {
setName() {
this.props.nameStore.setName(this.name);
}
render() {
return (
<form onSubmit={this.setName.bind(this)}>
<input type="text" ref={input => this.name = input} />
</form>
);
}
}
TEST
define('Name ponent', () => {
let markup;
beforeEach(() => {
const nameStore = new NameStore();
markup = mount(
<div>
<Component1 nameStore={nameStore} />
<Component2 nameStore={nameStore} />
</div>
);
});
it('should re-render name when updated', (done) => {
expect(markup.find('.name').text()).to.be.blank;
markup.find('form input').first().value = "john";
markup.find('form').simulate('submit');
expect(markup.find('.name').text()).to.equal("john is awesome")
});
});
For some reason, in the test, the actual value of {this.name}
in Component1
remains unchanged even though I'm able to verify that the setName
function in the store is being called properly and with the correct value.
Any help as to why Component1
is not re-rendering would be much appreciated.
Also, this is a contrived example as the actual example is proprietary..so forgive me if this example feels dumb :)
Thanks!
I am attempting to write a unit test for mobx-based reaction. For some reason, when the @observable
value is updated in an @action
, the @puted
function does not re-run as you'd expect.
Code:
STORE
class NameStore {
@observable name;
@action setName(name) {
this.name = name;
}
}
COMPONENT 1
@observer
class Name {
@puted get name() {
if (this.props.nameStore.name) {
return `${this.props.nameStore.name} is awesome!`;
}
return null;
}
render() {
return (
<div className="name">
{this.name}
</div>
);
}
}
COMPONENT 2
@observer
class Name {
setName() {
this.props.nameStore.setName(this.name);
}
render() {
return (
<form onSubmit={this.setName.bind(this)}>
<input type="text" ref={input => this.name = input} />
</form>
);
}
}
TEST
define('Name ponent', () => {
let markup;
beforeEach(() => {
const nameStore = new NameStore();
markup = mount(
<div>
<Component1 nameStore={nameStore} />
<Component2 nameStore={nameStore} />
</div>
);
});
it('should re-render name when updated', (done) => {
expect(markup.find('.name').text()).to.be.blank;
markup.find('form input').first().value = "john";
markup.find('form').simulate('submit');
expect(markup.find('.name').text()).to.equal("john is awesome")
});
});
For some reason, in the test, the actual value of {this.name}
in Component1
remains unchanged even though I'm able to verify that the setName
function in the store is being called properly and with the correct value.
Any help as to why Component1
is not re-rendering would be much appreciated.
Also, this is a contrived example as the actual example is proprietary..so forgive me if this example feels dumb :)
Thanks!
Share Improve this question asked Feb 19, 2018 at 22:58 MarkMark 11.1k6 gold badges34 silver badges48 bronze badges1 Answer
Reset to default 4There are few mistakes:
- Neither of the ponents extends
React.Component
@puted
should be in the storeinput
should bind toonChange
and updates itsvalue
- Browser may plain
input
value is undefined. It's better to set it to empty string. input
value is inevent.target.value
,ref={input => this.name = input}
will assignthis.name
to the html ponent.- The rule of thumb when deal with form is to call
event.preventDefault()
The code below is a pleted working example based on your code:
import React from 'react';
import { observable, action, puted } from 'mobx';
import { observer } from 'mobx-react';
class NameStore {
@observable name = '';
@action
setName = name => {
this.name = name;
}
@puted
get awesomeName() {
return this.name ? `${this.name} is awesome!` : '';
}
}
@observer
class NameField extends React.Component {
render() {
const { nameStore } = this.props;
return <div className="name"> {nameStore.awesomeName} </div>;
}
}
@observer
class NameInput extends React.Component {
render() {
const { nameStore } = this.props;
return (
<form>
<input
type="text"
onChange={this.onChange}
value={nameStore.name}
/>
</form>
);
}
onChange = e => {
const { nameStore } = this.props;
nameStore.setName(e.target.value);
e.preventDefault();
}
}
@observer
class App extends React.Component {
nameStore = new NameStore();
render() {
return (
<div>
<NameField nameStore={this.nameStore} />
<NameInput nameStore={this.nameStore} />
</div>
);
}
}
export default App;
Extra note: Since the arrow function has been used, I don't have to call .bind(this)
.