I use redux-sessionstorage to store a subset of my app's state to session storage. Part of that state indicates whether certain panels in the UI are expanded or collapsed (visible or hidden).
I have a sample app reproducing exactly this: https://github.com/shawninder/sessionstorage-bug
See the readme for complete reproduction steps.
Also hosted online here (give it a moment to unfreeze)
The problem happens when I reload the page. redux-sessionstorage correctly restores the previous state from sessionstorage and triggers a render, yet the style attribute I'm using to do the hiding and showing is not updated.
In other words, this works as expected:
<p>showNotice: {props.app.showNotice ? 'true' : 'false'}</p>
I can see showNotice: false
on page load, but it's then immediately replaced by showNotice: true
when redux-sessionstorage restores the saved state.
But the following doesn't work:
<div
className='notice'
style={{
opacity: props.app.showNotice ? 1 : 0,
transform: props.app.showNotice ? 'translateX(0)' : 'translateX(-100%)'
}}
>
Notice!
</div>
The page loads with the notice hidden (as expected), but it doesn't get shown when redux-sessionstorage restores the state. In fact, it doesn't show until I click the toggle button twice, the first time to set showNotice
back to false
, and then a second time to set it to true
and show the <div>
.
Why is redux-sessionstorage not updating the style
attribute here, and how do people generally handle this?
@lecstor pointed to the right thread about this issue.
This is a thing with Server Side Rendering (SSR). The workaround Dan Abramov described on this comment solved the issue.
You're using Next.js for SSR. When running your project on dev
mode, you can see the following error message on Chrome console, after pressing Toggle to true and reloading:
Warning: Text content did not match. Server: "false" Client: "true"
Since Next doesn't know anything about the saved state on the client side, it always returns app.showNotice
to false
. You can also check the returned content for GET localhost
on Chrome Network Tab.
Apparently Next.js
tries to merge the content returned from the server and the one rendered locally, but fails to merge the style. Apparently, it's an expected behaviour on Next and also on React 16 SSR, that you can overcome by triggering a second render()
or doing the implementation below.
class App extends Component {
constructor(props) {
super(props);
this.state = { showNotice: false };
}
componentDidMount() {
this.setState({ showNotice: this.props.app.showNotice });
}
componentWillReceiveProps(props) {
this.setState({ showNotice: props.app.showNotice });
}
render() {
return (
<div>
<p>showNotice: {this.state.showNotice ? 'true' : 'false'}</p>
<button onClick={(event) => {
this.props.dispatch({ type: 'App:toggleNotice' })
}}>toggle</button>
<div
className='notice'
style={{
opacity: this.state.showNotice ? 1 : 0,
transform: this.state.showNotice ? 'translateX(0)' : 'translateX(-100%)'
}}
>
Notice!
</div>
<style jsx>{`
.notice {
width: 100px;
transition-property: opacity, transform;
transition-duration: 0.4s;
}
`}</style>
</div>
)
}
}