I have a global service widgetService
which holds data for a number of widgets, each identified by a widgetID
. Each widget's data can change at any time. I want to display a widget with a React component, say WidgetReactComponent
.
The react component shall take a widget ID as property and get the information to display from the widget service. A widget's data can be queried from the widget service with the method getWidgetData(widgetID)
. And in order to be able to publish data changes, it also offers two methods: addListenerForWidget(widgetID, listener)
and removeListenerForWidget(widgetID, listener)
.
When assuming that the property is set once and never changed, this can be achieved like this, following React's recommendations:
class WidgetReactComponent extends Component {
constructor() {
super();
this.state = {
data: widgetService.getWidgetData(this.props.widgetID)
};
this._onDataChange = this._onDataChange.bind(this);
}
_onDataChange(newData) {
this.setState({data: newData});
}
componentDidMount() {
// React documentation: "This method is a good place to set up any subscriptions."
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
componentWillUnmount() {
// React documentation: "Perform any necessary cleanup in this method, such as [...] cleaning up any subscriptions that were created in componentDidMount()."
widgetService.removeListenerForWidget(this.props.widgetID, this._onDataChange);
}
render() {
return <div className="Widget">{this.state.data.stuff}</div>;
}
}
The component may then be used like this:
<ReactWidgetComponent widgetID={17} />
However, the widgetID
property may change at any time, and the component has to handle this in order to function properly under all circumstances. By react's recommendation, this should be handled by setting the state based on properties using the static getDerivedStateFromProps
function. But since it is static, I do not have access to the component and cannot change the listeners accordingly.
One way to work around this would be to store the widgetID
in the state, and then use the lifecycle method componentDidUpdate
to detect the change, like this:
constructor() {
super();
this._onDataChange = this._onDataChange.bind(this);
}
static getDerivedStateFromProps(nextProps) {
return {
widgetID: nextProps.widgetID,
data: widgetService.getWidgetData(nextProps.widgetID)
};
}
componentDidUpdate(prevProps, prevState) {
if (prevState.widgetID !== this.state.widgetID) {
widgetService.removeListenerForWidget(prevState.widgetID, this._onDataChange);
widgetService.addListenerForWidget(this.state.widgetID, this._onDataChange);
}
}
However, componentDidUpdate
won't be called when shouldComponentUpdate
returns false
. This doesn't feel like a safe way of doing this. Also I believe the listeners will be wrong for the entire timespan between the property change and the update's completion. How could I safely implement this?
You don't need to store widgetID
in state, you can compare prevProps
with this.props
:
componentDidUpdate(prevProps, prevState) {
if (prevProps.widgetID !== this.props.widgetID) {
widgetService.removeListenerForWidget(prevProps.widgetID, this._onDataChange);
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
}
You will also need to add the listener in componentDidMount
as componentDidUpdate
is not called on first rendering:
componentDidMount() {
widgetService.addListenerForWidget(this.props.widgetID, this._onDataChange);
}
Regarding your concerns:
componentDidUpdate won't be called when shouldComponentUpdate returns false
From the docs:
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props.
So if you decided to not update the component when this.props.widgetID
changes, then you are violating the assumption/purpose of shouldComponentUpdate
and should not expect your widget listener to be updated.
A lot of things will not work as intended if you misuse shouldComponentUpdate
anyway (eg. component not updated to reflect new data), so relying on an API being used correctly as per official docs is a necessity to achieve simplicity, rather than something to be avoided.
the listeners will be wrong for the entire timespan between the property change and the update's completion
By this logic, when you update some displayed data in an event handler, you can also claim that the data displayed is wrong for the entire timespan between the event and the re-rendering. You can even claim that your text editor is displaying the wrong data between the time you press a keyboard key and rendering of the key on the screen.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With