The examples I've seen of integrating React components with MobX stores seem to be tightly coupled. I would like to do this in a more reusable way, and would appreciate help understanding the "right" way to do this.
I wrote the following code (React + MobX + Typescript) to illustrate what I want to do and the issue I'm running into.
The store has multiple observable timestamps.
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
Let's say I want to make a reusable date input component allowing the user to enter a date for either startTimestamp, endTimestamp, or some other store property. More generally, I want to create a component that I can use to modify any arbitrary property of any store.
My best understanding of React/MobX integration is that components receive a MobX store, read observable properties of the store and may execute actions to modify those properties. However, this seems to assume that components are wired to the names of store properties, which makes them not fully reusable.
I've experimented with the following "proxy store" approach to expose the property I want to the component as "value":
class MyStoreTimestampProxy {
constructor(private store: MyStore, private propertyName: 'startTimestamp' | 'endTimestamp') {
}
@observable get value() {
return this.store[this.propertyName];
}
@action setValue(val: number) {
this.store[this.propertyName] = val;
}
};
const myStoreStartTimestamp = new MyStoreTimestampProxy(myStore, 'startTimestamp');
const myStoreEndTimestamp = new MyStoreTimestampProxy(myStore, 'endTimestamp');
However, I feel like I'm not doing things the React/MobX way somehow, and want to understand the best practice here. Thank you!
Full code follows:
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import { observable, action } from 'mobx';
import { observer } from 'mobx-react';
/***
* Initialize simple store
*/
class MyStore {
@observable value: number;
@action setValue(val: number) { this.value = val; }
@observable startTimestamp: number;
@action setStartTimestamp(val: number) { this.startTimestamp = val; }
@observable endTimestamp: number;
@action setEndTimestamp(val: number) { this.endTimestamp = val; }
}
const myStore = new MyStore();
myStore.setValue(new Date().getTime());
/**
* My time input component. Takes in a label for display, and a store for reading/writing the property.
*/
interface IDateInputProps {
label: string;
store: {
value: number;
setValue(val: number): void;
}
}
interface IDateInputState {
value: string;
}
@observer class DateInput extends React.Component<IDateInputProps, IDateInputState> {
constructor(props: IDateInputProps) {
super(props);
this.state = { value: new Date(props.store.value).toDateString() };
}
render() {
return (
<div>
<label>{this.props.label}
<input
value={this.state.value}
onChange={this.onChange.bind(this)} />
</label>
</div>
);
}
onChange(event) {
const date = new Date(event.target.value);
this.setState({ value: event.target.value });
this.props.store.setValue(date.getTime());
}
}
/**
* Test view
*
*/
class TestView extends React.Component {
render() {
return (
<div>
<DateInput label="Editing the value property of store: " store={myStore}></DateInput>
{/* How to create components for startTimestamp and endTimestamp */}
</div>
);
}
};
ReactDOM.render(<TestView />, document.getElementById('root'));