I have a stream of events which is grouped by name.
Every resulting observable is passed as events$
prop to a Folder
component, which subscribes to it in componentDidMount()
.
The problem I'm experiencing is that, Folder
's observer is missing the first event.
To reproduce, click on Add event. You'll see that Folder
renders undefined
, and doesn't update it to the data in the emitted event. Playground
const events$ = new Rx.Subject();
class Folder extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.props.events$.subscribe(e => {
this.setState({
name: e.name,
permissions: e.permissions
});
});
}
componentWillUnmount() {
this.props.events$.unsubscribe();
}
render() {
const { name, permissions } = this.state;
return (
<div>
{`[${permissions}] ${name}`}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super();
this.eventsByName$ = props.events$.groupBy(e => e.name);
this.state = {};
}
componentDidMount() {
this.eventsByName$.subscribe(folderEvents$ => {
const folder = folderEvents$.key;
this.setState({
[folder]: folderEvents$
});
});
}
onClick = () => {
this.props.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
};
render() {
const folders = Object.keys(this.state);
return (
<div>
<button onClick={this.onClick}>
Add event
</button>
<div>
{
folders.map(folder => (
<Folder events$={this.state[folder]} key={folder} />
))
}
</div>
</div>
);
}
}
ReactDOM.render(
<App events$={events$} />,
document.getElementById('app')
);
<head>
<script src="https://unpkg.com/rxjs@5.2.0/bundles/Rx.js"></script>
<script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
</head>
<body>
<div id="app"></div>
</body>
To get the missing event, I used ReplaySubject(1)
: Playground
const events$ = new Rx.Subject();
class Folder extends React.Component {
constructor() {
super();
this.state = {};
}
componentDidMount() {
this.props.events$.subscribe(e => {
this.setState({
name: e.name,
permissions: e.permissions
});
});
}
componentWillUnmount() {
this.props.events$.unsubscribe();
}
render() {
const { name, permissions } = this.state;
return (
<div>
{`[${permissions}] ${name}`}
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super();
this.eventsByName$ = props.events$.groupBy(e => e.name);
this.state = {};
}
componentDidMount() {
this.eventsByName$.subscribe(folderEvents$ => {
const folder = folderEvents$.key;
const subject$ = new Rx.ReplaySubject(1);
folderEvents$.subscribe(e => subject$.next(e));
this.setState({
[folder]: subject$
});
});
}
onClick = () => {
this.props.events$.next({
type: 'ADD_FOLDER',
name: 'js',
permissions: 400
});
};
render() {
const folders = Object.keys(this.state);
return (
<div>
<button onClick={this.onClick}>
Add event
</button>
<div>
{
folders.map(folder => (
<Folder events$={this.state[folder]} key={folder} />
))
}
</div>
</div>
);
}
}
ReactDOM.render(
<App events$={events$} />,
document.getElementById('app')
);
<head>
<script src="https://unpkg.com/rxjs@5.2.0/bundles/Rx.js"></script>
<script src="https://unpkg.com/react@15.4.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom@15.4.2/dist/react-dom.js"></script>
</head>
<body>
<div id="app"></div>
</body>
Now, when Add event is clicked, Folder
's observer sees the event and properly renders its data.
This feels a bit hacky to me.
Is there a better way to organize the code to avoid the missing event issue?
This question might help.