简体   繁体   中英

How to make react component not to re-render, when sibling re-renders?

OK, so I just started to play with react and redux, and have encountered problem. I have button which calls API, and receives info about some cars. It works like this:

on button click it dispatches function getCars() , this in turn dispatches pendingAction , then fetches info, then dispatches successAction or errorAction . (I'll show all code bellow).

My problem is:

while loading new info it changes states to pending, and then re-renders pictures, even though they have same src . I want, to avoid re-render as, it makes pictures flash to white for a second.

样本

I have my app set up like this:

//index.js

import App from './App';
import * as serviceWorker from './serviceWorker';
import { applyMiddleware, createStore, compose } from 'redux';
import { Provider } from 'react-redux';
import Reducers from './/Reducers';
import thunk from 'redux-thunk';

const middlewares = [thunk];

const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(Reducers, composeEnhancers(
    applyMiddleware(...middlewares)
));  

ReactDOM.render(
   <Provider store={store}>
    <App />
   </Provider>,
  document.getElementById('root')
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: somelink 
serviceWorker.unregister();

Then my app.js

//App.js

import React from 'react';
import 'rsuite/dist/styles/rsuite-default.css';
import { useSelector, useDispatch } from 'react-redux';
import { increment, decrement, login, logout } from './/Actions/TestingActions';
import  GetCars  from './/API/Cars/GetCars';
import { Button } from 'rsuite';
import CarView from './Components/CarTestView/CarView'
//import './index.css';


function App() {
    const counter = useSelector(state => state.count)
    const logged = useSelector(state => state.loggedin)
    const dispatch = useDispatch()

    return (
    <div className="App">
            //  
            //   I hidden some unrelated code here  ...              
            //  
            <Button onClick={() => dispatch(GetCars())}>Getcars</Button>
            <CarView />
    </div>
  );
}

export default App;

GetCars...

//GetCars.js

import { apiCarsError, apiCarsSuccess, apiCarsPending } from '../../Actions/TestingActions';
export function GetCars() {    
    return dispatch => {
        dispatch(apiCarsPending());
        fetch('https://localhost:44342/API/GetRandomCar')
            .then(res => {
                res.json().then(res => {
                    if (res.error) {
                        throw (res.error);
                    }
                    dispatch(apiCarsSuccess(res));
                    return res;
                })
                    .catch(error => {
                        dispatch(apiCarsError(error));
                    })
            });
            
    }
}

export default GetCars;

finally CarView.

import React from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { GetCars } from '../../API//Cars//GetCars';
import { getCars, getCarsPending, getCarsError } from '../../Reducers//TestingReducer';
import { Loader, Placeholder, Panel, /*PanelGroup*/ } from 'rsuite';
//import { CSSTransition, TransitionGroup  } from 'react-transition-group';
const { Paragraph } = Placeholder;

function CarView() {
    const pending = useSelector(state => state.API.pending)
    //const error = useSelector(state => state.API.error)
    const cars = useSelector(state => state.API.cars)
    if (pending && cars.length === 0) return (    
        <div>
            {console.log("Update is nulio")}
                <Loader backdrop content="loading..." vertical />
                <Paragraph rows={8}></Paragraph>       
            </div>          
        )

    if (pending) return (   
        <div>
            {console.log("Update pending")}
                <Loader center content="keiciam metus" />
            <div>
                    <Carvaizdas cars={cars} updatePicture={false} />
            </div>
            </div>
        )

    if (cars.length === 0) return (<div>{console.log("tuscia")}</div>)

    return (
        <div>               
            {console.log("uzkrauta || new info")}
                <div>
                    <Carvaizdas cars={cars} updatePicture={true} />
                </div>
            </div>
        )
}

class Carvaizdas extends React.PureComponent {

    shouldComponentUpdate() {      
        console.log("Should render ?"); 
        console.log(this.props.updatePicture); 
        return this.props.updatePicture;
    }

    render() {
        console.log("render cars");        
        return (
            <>
       <h1>Masinos</h1>
                {this.props.cars.map(car => <CarKorta car={car}/>)}
            </>
        );
    }
}

class CarKorta extends React.PureComponent {
    render() {
        return (
            <Panel shaded bordered bodyFill style={{ display: 'inline-block', width: 240, margin: 10 }}>
                <div style={{ height: 150, width: 240, display: 'flex', alignItems: 'center', justifyContent: 'center', paddingTop: 10 }}>
                    <div style={{ height: 'auto', width: 220 }}>
                        <img src={this.props.car.picture} /*height="240"*/ style={{ maxHeight: 150, height: 'auto', width: 220, borderRadius: 5, boxShadow: "1px 1px 2px #666" }} />
                    </div>
                </div>
                <Panel header={this.props.car.make}>
                    <p>
                        Year: {this.props.car.year}
                        <br />
                        Model: {this.props.car.model}
                    </p>
                </Panel>
            </Panel>
        );
    }
}

const mapStateToProps = state => ({
    error: getCarsError(state),
    cars: getCars(state),
    pending: getCarsPending(state)
})

const mapDispatchToProps = dispatch => bindActionCreators({
    CarView: GetCars()
}, dispatch)

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(CarView);
    
        

Thanks for your help.

The problem is in using 2 instances of component Carvaizdas under a different condition. This makes no sence for shouldComponentUpdate hook which is specific PER INSTANCE.

if (pending)
  return (
    <div>
      {console.log("Update pending")}
      <Loader center content="keiciam metus" />
      <div>
        <Carvaizdas cars={cars} updatePicture={false} /> {/** first component instance */}
      </div>
    </div>
  );

if (cars.length === 0) return <div>{console.log("tuscia")}</div>;

return (
  <div>
    {console.log("uzkrauta || new info")}
    <div>
      <Carvaizdas cars={cars} updatePicture={true} /> {/** second component instance */}
    </div>
  </div>
);

In order to shouldComponentUpdate to work there should be only single instance

return (
  <div>
    {console.log("uzkrauta || new info")}
    <div>
      <Carvaizdas cars={cars} />
    </div>
  </div>
);

And in this component using shouldComponentUpdate makes no sense too

class Carvaizdas extends React.PureComponent {
  render() {
      console.log("render cars");        
      return (
          <>
     <h1>Masinos</h1>
              {this.props.cars.map(car => <CarKorta car={car}/>)}
          </>
      );
  }
}

It only makes sense for CarKorta . You should remove shouldComponentUpdate from Carvaizdas and add it to CarKorta . Also you will have to store the previous picture in CarKorta state in order to be able to compare it with next picture. For this you have to use getDerivedStateFromProps

class CarKorta extends React.PureComponent {
  state = {
    car: null,
  };

  shouldComponentUpdate(nextProps) {
    return !this.state.car || this.state.car.picture !== nextProps.car.picture;
  }

  static getDerivedStateFromProps(nextProps, prevState) {
    return {
      car: { ...nextProps.car },
    };
  }

  render() {
    return (
      <Panel
        shaded
        bordered
        bodyFill
        style={{ display: "inline-block", width: 240, margin: 10 }}
      >
        <img
          src={this.state.car.picture}
          /*height="240"*/ style={{
            maxHeight: 150,
            height: "auto",
            width: 220,
            borderRadius: 5,
            boxShadow: "1px 1px 2px #666",
          }}
        />
      </Panel>
    );
  }
}

I change CarView like this and it works now.

import React from 'react';
import { connect, useSelector } from 'react-redux';
import { bindActionCreators } from 'redux';
import { GetCars } from '../../API//Cars//GetCars';
import { getCars, getCarsPending, getCarsError } from '../../Reducers//TestingReducer';
import { Loader, Placeholder, Panel, /*PanelGroup*/ } from 'rsuite';
//import { CSSTransition, TransitionGroup  } from 'react-transition-group';
const { Paragraph } = Placeholder;




function CarView() {
    const pending = useSelector(state => state.API.pending)
    //const error = useSelector(state => state.API.error)
    const cars = useSelector(state => state.API.cars)
    if (pending && cars.length === 0) return (    
        <div>
            {console.log("Update is nulio")}
                <Loader backdrop content="loading..." vertical />
                <Paragraph rows={8}></Paragraph>       
            </div>          
        )

    if (cars.length === 0) return (<div>{console.log("tuscia")}</div>)

    return (
        <div>               
            {console.log("uzkrauta || new info")}
            {pending ? <Loader center content="keiciam metus" />: <></>}
                <div>
                    <Carvaizdas cars={cars} />
                </div>
            </div>
        )
}

class Carvaizdas extends React.PureComponent {
    render() {
        console.log("render cars");        
        return (
            <>
        <h1>Masinos</h1>
                {this.props.cars.map(car => <CarKorta car={car}/>)}
            </>
        );
    }
}

class CarKorta extends React.PureComponent {
    render() {
        return (
            <Panel shaded bordered bodyFill style={{ display: 'inline-block', width: 240, margin: 10 }}>
                <div style={{ height: 150, width: 240, display: 'flex', alignItems: 'center', justifyContent: 'center', paddingTop: 10 }}>
                    <div style={{ height: 'auto', width: 220 }}>
                        <img src={this.props.car.picture} /*height="240"*/ style={{ maxHeight: 150, height: 'auto', width: 220, borderRadius: 5, boxShadow: "1px 1px 2px #666"     }} />
                    </div>
                </div>
                <Panel header={this.props.car.make}>
                        <p>
                        Year: {this.props.car.year}
                        <br />
                        Model: {this.props.car.model}
                    </p>
                </Panel>
            </Panel>
        );
    }
}

const mapStateToProps = state => ({
    error: getCarsError(state),
    cars: getCars(state),
    pending: getCarsPending(state)
})

const mapDispatchToProps = dispatch => bindActionCreators({
    CarView: GetCars()
}, dispatch)

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(CarView);
    
        

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM