when i'm updating my state using setState from parent component my child component get rendering(because props getting change)
Parent component
addonsHandler =(addons) =>{
this.setState({addons:addons}, () => {
// console.log(this.state.addons);
});
};
render() {
return (
<div>
<Row>
<Col span={15} offset={2}>
<AntForm pickupHandler= {this.pickupHandler} dropHandler={this.dropHandler} addonsHandler={this.addonsHandler} ambulanceTypeHandler={this.ambulanceTypeHandler}/>
<Button type="primary" onClick={this.drop} >Drop</Button>
<Button type="primary" onClick={this.calculateRoute}>Direction</Button>
{/*<div id="map" style={{height: "600px"}}></div>*/}
<Map onRef={ref => (this.MapRef = ref)} />
</Col>
<Col span={6} offset={1}>
<BookingDetails addons={this.state.addons} price={this.addonObj} ambulaceType={this.state.AmbulanceType} VehiclePrice={this.ambulacneTypeObj} />
</Col>
</Row>
<Row>
<Col span={15} offset={2}>
</Col>
</Row>
</div>
);
}
so i want to stop rendering only Map component when addons state get change in parent component
so i used shouldComponentUpdate in Map component but it's not stoping rendering to component
shouldComponentUpdate(nextProps, nextState) {
return false;
}
shouldComponentUpdate() affect on parent component. If it returns true, parent component will be rerender.
So, I think you should move shouldComponentUpdate() into BookingDetails component instead.
Related
I have two different react components placed one after the other in my app named SearchBar and InfiniteScroller;
function App() {
const [searchTerm, setSearchTerm] = useState("");
return (
<div className="App">
<SNavbar></SNavbar>
<MainLogo></MainLogo>
<SearchBar search={setSearchTerm}></SearchBar>
<hr/>
<InfiniteScroller term={searchTerm}/>
<Footer/>
</div>
);
}
The search bar component has its own state where it updates a search term as its input is being edited and it calls the setSearch function of its parent when the button is clicked (the function is passed as a prop in the parent)
function SearchBar(props)
{
const [search,setSearch] = useState("");
return(
<Container className="Search-Bar">
<Row>
<Col>
<InputGroup >
<FormControl
placeholder="What are we making today?"
onChange={event => setSearch(event.target.value)}
/>
<Button onClick={() => props.search(search)}>
Go!
</Button>
</InputGroup>
</Col>
</Row>
</Container>)
}
The search term that is updated by the SearchBar component is passed onto the InfiniteScroller component as a property and is set as the searchTerm field in its state object.
class InfiniteScroller extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
items:[],
page:1,
hasMore:true,
searchTerm:props.term
};
}
render(){
return(
<InfiniteScroll
dataLength={this.state.items.length}
next={this.fetchData}
hasMore={this.state.hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
>
<Row>
{this.state.items.map((i, index) => (
<Col key={index} lg="2" md="4" sm="6" xs="12">
<ImageCell className="ImageCell" link = {this.state.items[index].link}> - #{index}</ImageCell>
</Col>
))}
</Row>
</InfiniteScroll>
)
}
}
However when the setSearchTerm function of App.js is triggered by pressing the button on the SearchBar component, the InfiniteScroller does not seem to get updated. As the SearchTerm field of its state still comes up as "undefined" and the component itself does not re-render to represent the change in property.
I want the InfiniteScroller to completely re-render itself and make some API calls to populate itself with content, How can I achieve this?
So far I've tried adding in HTML tags that have the SearchTerm property in them to check if react skips re-rendering components that don't "use" any properties but that has not worked.
The props' change does not make the UI re-rendering but the states' change does.
It has 2 potential ways to fix have a proper UI re-rendering.
For the first one, you can add key attribute to your component that will help you do a trick for re-rendering whenever key gets changed
<InfiniteScroller term={searchTerm} key={searchTerm}/>
The second way, you can update your local states of that component by componentDidUpdate (useEffect in function-based components)
class InfiniteScroller extends React.Component
{
constructor(props)
{
super(props);
this.state =
{
items:[],
page:1,
hasMore:true,
searchTerm:props.term
};
}
//update states according to props change
componentDidUpdate(prevProps) {
if(this.props.searchTerm !== prevProps.searchTerm) {
setState({ searchTerm: this.props.searchTerm })
}
}
render(){
return(
<InfiniteScroll
dataLength={this.state.items.length}
next={this.fetchData}
hasMore={this.state.hasMore}
loader={<h4>Loading...</h4>}
endMessage={
<p style={{ textAlign: 'center' }}>
<b>Yay! You have seen it all</b>
</p>
}
>
<Row>
{this.state.items.map((i, index) => (
<Col key={index} lg="2" md="4" sm="6" xs="12">
<ImageCell className="ImageCell" link = {this.state.items[index].link}> - #{index}</ImageCell>
</Col>
))}
</Row>
</InfiniteScroll>
)
}
}
I have my React app defined as such:
function App() {
const [clickedNode, setClickedNode] = useState({});
const [hoveredLink, setHoveredLink] = useState({});
const updateNode = useCallback((node) => { setClickedNode(node); });
return (
<Container fluid>
<Row>
<Col sm={8} >
<MemoGraph setClickedNode={updateNode} />
</Col>
<Col sm={4}>
<InfoSection node={clickedNode}
link={hoveredLink} />
</Col>
</Row>
</Container >
);
}
in MemoGraph, there is a callback in a graph vizualization that updates the ClickedNode state variable.
This is really just to rerender the InfoSection object, but it causes the whole component to rerender.
The MemoGraph reRendering is really expensive, and needs to stop. In DevTools, the App is said to rerender because of the Hooks changing, which I do not understand. Is there someone that can explain the best way to change parent state in a child component, but so only InfoSection renders?
Any help would be really appreciated!
so the initial problem like this, I have a component select the language that is imported into the login component, from the language component when the onChange event I hit an API then the response from the API I put into local storage, when I retrieve data from local storage to be called in login component and entered into the login component state, the data is not updated as local storage, but if the local storage data is successfully updated, how to update the state in the lifecycle react?
this is state from component login
constructor(props){
super(props)
this.state = {
getDataLocal:[]
}
}
and I want get data from localstorage and update to state
componentDidUpdate(){
//set state and get data from localstorage
this.setState({
getDataLocal:JSON.parse(this.props.resGet('toLocalPages')),
loading:false
})
}
unsuccessfully updated and still contains an empty array
render() {
const { loading,getDataLocal} = this.state
//getDataLocal unsuccessfully updated and still contains an empty array
console.log(getDataLocal)
if(this.state.redirect){
return <Redirect to='/home' />
}
//untuk menghandle data di localstorage
if(this.props.resGet('data')){
return <Redirect to='/home' />
}
if(loading){
return(
<p>loading</p>
)
}else{
return (
<React.Fragment>
<Container>
<Padding />
<Row>
<Col md={{ span: 6, offset: 3 }} className='mx-auto'>
<Card>
<CardContent>
<Row>
<Col xs={12}>
<h3><b>{getDataLocal.page_login.title}</b></h3>
<p><b>{getDataLocal.page_login.subtitle}</b></p>
<br />
</Col>
</Row>
<Form onSubmit={this.handleSubmit}>
<SelectLanguage />
<Form.Group controlId='formBasicEmail'>
<Form.Label><b>{getDataLocal.page_login.label1}</b></Form.Label>
<Form.Control type='email' name='MEMBER_EMAIL' placeholder={getDataLocal.page_login.placeholder_label1} onChange={this.handleChange} />
</Form.Group>
<Form.Group controlId='formBasicPassword'>
<Form.Label><b>{getDataLocal.page_login.label2}</b></Form.Label>
<Form.Control type='password' name='MEMBER_PASSWORD' placeholder={getDataLocal.page_login.placeholder_label2} onChange={this.handleChange} />
</Form.Group>
<p className='text-muted float-right'><b>{getDataLocal.page_login.forgot_password}</b></p>
<Button type='submit' variant='warning' className='text-white' size='md' block >{getDataLocal.page_login.button}</Button>
</Form>
<br/>
<p className='text-muted text-center'><b>Or</b></p>
<p className='text-primary text-center'><b><i className='fa fa-facebook-official' aria-hidden='true'></i> {getDataLocal.page_login.link_login}</b></p>
<br />
<p className='text-muted text-center'><b>{getDataLocal.page_login.text2} <Link to='/register'><span className='text-warning'>{getDataLocal.page_login.link_register}</span></Link></b></p>
</CardContent>
</Card>
</Col>
</Row>
</Container>
</React.Fragment>
)
}
}
}
You may call setState() immediately in componentDidUpdate() but note that it must be wrapped in a condition.
Note-
componentDidUpdate() will not be invoked if shouldComponentUpdate() returns false.
https://reactjs.org/docs/react-component.html#componentdidupdate
Two things, first you can't call componentDidUpdate directly, you have to check if the props or the state of the component change in order to perform some action, second, you're trying to assign a state loading that you didn't define in the first place, you do something like this:
state = {
getDataLocal: [],
loading: false
}
componentDidUpdate(prevProps) {
const { resGet } = this.props;
if (prevProps.resGet('toLocalPages') !== resGet('toLocalPages')) {
// Set state and get data from localstorage
this.setState({
getDataLocal: JSON.parse(resGet('toLocalPages')),
loading: false
})
}
}
Hope this helps.
I guess the problem is that changing props does not call render. Seems like getDerivedStateFromProps would help you, as it helped me in same situations before.
static getDerivedStateFromProps(props, state) {
return{
getDataLocal:JSON.parse(this.props.resGet('toLocalPages'))
};
}
Just try it and let me know if it works.
I have a following example simple page:
App.js:
export default class App extends React.Component {
render() {
return <Router>
<Switch>
<Route exact path='/' component={ArticlesPage}/>
<Route path='/search' component={SearchPage}/>
</Switch>
</Router>
};
};
ArticlesPage.js:
export default class ArticlesPage extends React.Component {
constructor(props) {
super(props);
}
render() {
return <Grid>
<Row>
<Col lg={12}>
<SearchBox/>
</Col>
</Row>
<Row>
<Col lg={12}>
articles
</Col>
</Row>
</Grid>;
}
};
SearchPage.js:
export default class SearchPage extends React.Component {
constructor(props) {
super(props);
const {q} = queryString.parse(location.search);
this.state = {
query: q
};
}
render() {
return <Grid>
<Row>
<Col lg={12}>
<SearchBox/>
</Col>
</Row>
<Row>
<Col lg={12}>
search {this.state.query}
</Col>
</Row>
</Grid>;
}
};
SearchBox.js:
export default class SearchBox extends React.Component {
constructor(props) {
super(props);
this.state = {
q: ''
};
}
onFormSubmit = (e) => {
e.preventDefault();
const {router} = this.context;
router.history.push('/search?q=' + this.state.q);
};
handleChange = (e) => {
this.setState({q: e.target.value});
};
render() {
return <form onSubmit={this.onFormSubmit}>
<Col lg={10} lgOffset={1}>
<FormGroup>
<input type="text" name="q" id="q" ref={i => this.searchInput = i} onChange={this.handleChange} />
</FormGroup>
</Col>
</form>;
}
};
And now, when I'm on the index page and type something in the input next send form, React render SearchPage.js and return correctly text search *and what I typed*, try again type something else in the input and send form, and React still show my previous text (not rerender).
What can be wrong with this simple page?
You have two different state variables, query on <SearchPage /> and q on <SearchBox />. What you are changing is q, but the variable you are rendering as text is query.
You need to lift state up and pass query as prop to <SearchPage />.
Here's why the text on SearchPage doesn't update: the constructor runs once and updates the variable in state, but when the app re-renders, React, wanting to be efficient, sees that it would re-render a new SearchPage in the same spot as the previous one, so instead of replacing it, it keeps the state of the old one. Because of this, SearchPage's state still keeps the old q variable.
Here's how you can fix it: make your SearchPage accept the search query as a prop, and render that.
class SearchPage extends React.Component {
render() {
return (
<Grid>
<Row>
<Col lg={12}>
<SearchBox />
</Col>
</Row>
<Row>
<Col lg={12}>search {this.props.query}</Col>
</Row>
</Grid>
)
}
}
In the parent, where the route for it is being rendered, use a render function, take the props of it, parse the actual query from props.location.search, and pass it directly to SearchPage.
<Route
path="/search"
render={props => <SearchPage query={getSearchQuery(props.location.search)} />}
/>
// utility function to keep things clean
function getSearchQuery(locationSearch) {
return queryString.parse(locationSearch.slice(1)).q
}
Here's a working demo.
I've written a container component in ReactJS and am passing in a prop to that component which is to be rendered as the 'main' content, like so:
class RegistrationContainer extends Component {
render() {
const MainContent = this.props.mainContent;
return (
<Row>
<Col offset="lg-3" lg={6}>
<MainContent />
</Col>
</Row>
);
}
}
export default RegistrationContainer;
And I'm passsing to it a mainContent prop like so:
import RegistrationContainer from './RegistrationContainer';
import RegistrationEntryView from './RegistrationEntryView';
class RegistrationCodeEntry extends Component {
render() {
return (
<RegistrationContainer mainContent={RegistrationEntryView} />
);
}
}
export default RegistrationCodeEntry;
My issue is that I would like RegistrationEntryView to have props, but can't seem to figure out how to define/pass in props on it. If I do the following I get an error:
class RegistrationCodeEntry extends Component {
render() {
const RegistrationView = <RegistrationEntryView someProp="blah" /> ;
return (
<RegistrationContainer mainContent={RegistrationView} />
);
}
}
export default RegistrationCodeEntry;
Error is as follows:
invariant.js?7313:42 Uncaught Error: Element type is invalid: expected
a string (for built-in components) or a class/function (for composite
components) but got: object. Check the render method of
RegistrationContainer.
Is this something that this.props.children could solve? I've been struggling to get my head around the concept of that, so any advice on where I'm going wrong would be appreciated.
You can solve this with this.props.children like this
class RegistrationCodeEntry extends Component {
render() {
return (
<RegistrationContainer>
// Render it as children
<RegistrationEntryView someProp="blah" />
</RegistrationContainer>
);
}
}
then in your container
class RegistrationContainer extends Component {
render() {
const MainContent = this.props.mainContent;
return (
<Row>
<Col offset="lg-3" lg={6}>
// Render the passed children
{this.props.children}
</Col>
</Row>
);
}
}
Your approach is correct. You just went wrong here:
<Row>
<Col offset="lg-3" lg={6}>
<MainContent />
</Col>
</Row>
Instead do this:
<Row>
<Col offset="lg-3" lg={6}>
{ MainContent }
</Col>
</Row>
I personally think, this approach is better than using children.
When you did this - const RegistrationView = <RegistrationEntryView someProp="blah" /> ; The component was already rendered and converted to appropriate format. Hence you cannot re-render it with <MainContent />.
So using {} is correct in this case.
Good Luck!