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.