Reputation: 62
I am struggling with a component that re-renders.
I'm using Redux to manage my states. In a component, I'm using a property (isPlaying: bool) from my state with mapStateToProps in some methods of my component (class) except for the render function, and I use some action creators to dispatch a change for isPlaying. By the way, I am using isPlaying in another child component(Music Player) using connect(react-redux).
This is what I'm expecting:
When isPlaying change, the parent component doesn't re-renders, and the Music Bar re-render and I keep using isPlaying in my parent component's methods.
What's the problem:
The parent component re-renders even if I'm not using isPlaying in the render method.
Sorry if I did confuse you while reading I am not an English native speaker.
Thanks in advance.
Edit: Add a simplified version of the code.
My Initial state:
export default {
// Some Irrelevant Properties
...
playlist: {
isPlaying: false,
playingTrack: null,
nextTrack: null,
prevTrack: null,
views: null,
totalListens: null,
tracks: []
}
};
Here's my parent component. (App.js)
import ...
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
widgets: [],
links: [],
clickedTrack: null
}
this.musicBarRef = React.createRef()
this._isMounted = false;
// Binding this to the methods used in this Component
...
}
// All my methods here
// Example of one function
playTrack() {
let {isPlaying} = this.props
if (isPlaying) {
// Pause
} else {
// Play
}
}
render() {
<>
// Some irrelevant components (don't use isPlaying)
<TracksPlaylist tracks={this.props.tracks} and some the methods created above />
<MusicBar ref={this.musicBarRef} and some methods created above />
// Example of one irrelevant component that re-renders
<Footer />
</>
}
}
const mapStateToProps = (state) => {
return {
isPlaying: state.playlist.isPlaying,
selectedTrack: state.playlist.playingTrack,
nextTrack: state.playlist.nextTrack,
prevTrack: state.playlist.prevTrack,
tracks: state.playlist.tracks,
}
}
const mapDispatchToProps = {
setPlaying: startPlaying, // Set isPlaying: true
setNotPlaying: stopPlaying, // Set isPlaying: false
// Some Irrelevent Action Creators
...
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Here's my reducer
import * as types from "../actions/actionTypes";
import initialState from "./initialState";
export default function musicPlayerReducer(state = initialState.playlist, action) {
switch (action.type) {
// Some irrelevant cases
...
case types.STOP_PLAYING:
return {...state, isPlaying: false}
case types.START_PLAYING:
return {...state, isPlaying: true}
case types.NEXT_TRACK_FOUND:
return {...state, nextTrack: action.track}
case types.PREV_TRACK_FOUND:
return {...state, prevTrack: action.track}
case types.CURRENT_TRACK_FOUND:
return {...state, playingTrack: action.track}
default:
return state
}
}
TracksPlaylist mentioned in App.js
import ...
class TracksPlaylist extends React.Component {
constructor(props) {
super(props)
}
render() {
let {tracks, onPlayClick ... and other methods from App.js} = this.props
return (
<div className="songs">
{
Object.values(tracks).length > 0 ?
Object.values(tracks).map((item, index) => {
return <Track onPlayClick={onPlayClick} key={index} item={item} />
}) :
''
}
</div>
)
}
}
const mapStateToProps = (state) => {
return {
tracks: state.playlist.tracks,
}
}
const mapDispatchToProps = {
setPlaying: startPlaying,
setNotPlaying: stopPlaying,
// Some irrelevant functions
...
}
export default connect(mapStateToProps, mapDispatchToProps)(Tracks)
the Track component
import ...
class Track extends React.Component {
constructor(props) {
super(props)
}
hasInteraction(interactionType, trackId) {
return false
}
render() {
let {onPlayClick, item} = this.props
return (
<div key={item.track_id} className="track" id={item.track_id} data-category={item.category_name}>
// A lot of irrelevant JSX :)
<TrackAction onPlayClick={onPlayClick} item={item} />
</div>
</div>
)
}
}
export default Track
Here's the TrackAction (it uses the isPlaying):
import ...
function TrackAction({item, isPlaying, playingTrack, onPlayClick}) {
return (
<div className="status action play-track" onClick={onPlayClick}>
<i id={item.track_id} className={isPlaying && playingTrack.track_id === item.track_id ? 'fas fa-pause' : 'fas fa-play'} />
</div>
)
}
const mapStateToProps = (state) => {
return {
isPlaying: state.playlist.isPlaying,
playingTrack: state.playlist.playingTrack
}
}
export default connect(mapStateToProps)(TrackAction)
I did use TrackAction to make just this component re-render because isPlaying changing and it is registered to this component and used in its render().
Thanks again.
Upvotes: 0
Views: 649
Reputation: 1207
Although you are not using isPlaying
inside render
method, you are still subscribing to the change due to mapStateToProps
binding.
Whenever the State || Props
changes, React will just re-render the new updated state || props
with just shallow comparison.
And this is the reason why your parent is getting re-rendered.
One potential solution is to override shouldcomponentupdate
https://reactjs.org/docs/react-component.html#shouldcomponentupdate
Use shouldComponentUpdate() to let React know if a component’s output is not affected by the current change in state or props. The default behavior is to re-render on every state change.
Defaults to true. If you override and returns false, then UNSAFE_componentWillUpdate(), render(), and componentDidUpdate() will not be invoked.
Sample code :
shouldComponentUpdate(nextProps, nextState) {
return <enter_your_condition_to_be_true>; // else false
}
Upvotes: 1