update state inside react lifecycle not work - javascript

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.

Related

How to pass props through a parent between two react components

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>
)
}
}

shouldComponentUpdate not working in child component React js

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.

ReactJS component doesn't re-render

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.

Modal not showing proper video in react

Hi I'm implementing a sample app with react,I'm also using youtube api search and react bootstrap.
The idea is the following, I enter a search term, for example "PS4" this will call youtube api search and will bring back 5 matching results in a json format.
With this json, I render a list of thumbnails with videos, title, description,(similar to youtube website). if I click on one of the thumbnails, the idea is to display a modal dialog with the video inside. Here's the problem, it's always loading the same video.
I omit the imports at the beginning of the file
VideoList.js
class VideoList extends React.Component {
constructor(props) {
super(props);
this.openModal = this.openModal.bind(this);
this.close = this.close.bind(this);
this.state = {
showModal: false
}
}
openModal(video) {
this.setState({ showModal: true });
}
close() {
this.setState({ showModal: false });
}
render() {
const videoItems = this.props.videos.map(video =>
<Col xs={6} md={4}>
<Thumbnail onClick={() => this.openModal(video)}
className="thumbnail"
src={video.snippet.thumbnails.default.url}
alt={video.snippet.description}
key={uid()}
>
<h3>{video.title}</h3>
<p>{video.snippet.description}</p>
<hr />
<i>Date: {video.snippet.publishedAt}</i>
<VideoView show={this.state.showModal} video={video} close={this.close} />
</Thumbnail>
</Col>
);
return (
<Grid>
<Row>
{videoItems}
</Row>
</Grid>
);
}
}
export default VideoList;
VideoView.js
const VideoView = props => {
if (!props) {
return <div>{'Could not load the video'}</div>
}
return (
<div className="static-modal">
<Modal show={props.show}>
<Modal.Header>
<Modal.Title>{props.video.snippet.title}</Modal.Title>
</Modal.Header>
<Modal.Body>
One fine body...
</Modal.Body>
<Modal.Footer>
<Button onClick={() => props.close()}>Close</Button>
</Modal.Footer>
</Modal>
</div>
);
}
export default VideoView;
If I click on one of the thumbnails, a modal popup shows with the video (some information of it, not the video itself), the thing is that it always shows the last video (the last in the list of thumbnails)
Another thing: I'm facing a design problem here because I think I should delegate the onclose action to the modal dialog, in this case, the VideoView however the videoView component is a stateless component, I'm only sending some props to render it, (check the onclose callback). Is this a correct approach? Thanks!
You create multiple VideoView elements sharing the same prop this.state.showModal. It means that when this.state.showModal is true then you show all modals at once, and you see only one of them which is on top. Create one VideoView element and pass it a video to show. Also to show/hide modal you can check if this.state.video is not equal to null - if it have value assigned then modal should be visible.
class VideoList extends React.Component {
constructor(props) {
super(props);
this.openModal = this.openModal.bind(this);
this.close = this.close.bind(this);
this.state = {
video: null
}
}
openModal(video) {
this.setState({ video: video });
}
close() {
this.setState({ video: null });
}
render() {
const videoItems = this.props.videos.map(video =>
<Col xs={6} md={4}>
<Thumbnail onClick={() => this.openModal(video)}
className="thumbnail"
src={video.snippet.thumbnails.default.url}
alt={video.snippet.description}
key={uid()}
>
<h3>{video.title}</h3>
<p>{video.snippet.description}</p>
<hr />
<i>Date: {video.snippet.publishedAt}</i>
</Thumbnail>
</Col>
);
return (
<div>
<Grid>
<Row>
{videoItems}
</Row>
</Grid>
<VideoView show={this.state.video !== null} video={this.state.video} close={this.close} />
</div>
);
}
}
export default VideoList;

How to create this react modal the right way?

I have been working on my first Meteor application and am a bit stuck. I want to create my code following the latest guidelines (ES6 and React 15) but I am confused with all the recent changes in Javascript.
I want to add a Bootstrap Modal in my current comments list but can't seem to figure out how to add my content to the modal using the right up to date syntax.
Here is my current code:
In comment.js:
import React from 'react';
import { Row, Col, ListGroupItem, FormControl, Button } from 'react-bootstrap';
import { Bert } from 'meteor/themeteorchef:bert';
import { CommentsModal } from './comments-modal'
export const Comment = ({ comment }) => (
<ListGroupItem key={ comment._id }>
<Row>
<Col xs={ 8 } sm={ 10 }>
<FormControl
type="text"
defaultValue={ comment.title }
/>
</Col>
<Col xs={ 4 } sm={ 2 }>
<Button
bsStyle="danger"
className="btn-block">
Remove Comment
</Button>
</Col>
</Row>
<CommentsModal/>
</ListGroupItem>
);
In Comments-modal.js:
import React, { Component } from 'react';
import { Modal, Button, Tooltip } from 'react-bootstrap';
export class CommentsModal extends Component {
constructor(props) {
super(props);
this.state = {
showModal: false,
};
this.close = this.close.bind(this);
this.open = this.open.bind(this);
}
close() {
this.setState({ showModal: false });
}
open() {
this.setState({ showModal: true });
}
render() {
return (
<div>
<Button
bsStyle="primary"
bsSize="large"
onClick={this.open}
>
</Button>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title >Modal heading</Modal.Title>
</Modal.Header>
<Modal.Body>
<h4>Text in a modal</h4>
</Modal.Body>
<Modal.Footer>
<Button onClick={this.close}>Close</Button>
</Modal.Footer>
</Modal>
</div>
);
}
}
And last comments-list.js:
import React from 'react';
import { ListGroup, Alert } from 'react-bootstrap';
import { Comment } from './comment';
export const CommentsList = ({ comments }) => (
comments.length > 0 ? <ListGroup className="comments-list">
{comments.map((com) => (
<Comment key={ com._id } comment={ com } />
))}
</ListGroup> :
<Alert bsStyle="warning">No comments yet. Please add some!</Alert>
);
CommentsList.propTypes = {
comments: React.PropTypes.array,
};
I manage to get the Modal to show up and work but when I want to display data in it, I can't get it to work. What is the best way to combine both these into one?
Pass the data in props to the CommentsModal and render it as you would normally do.
I try to keep local state out of component when using redux if possible, so to answer your question on making it stateless, I would take the following steps:
Remove the button that opens the modal from the modal.js itself
Remove the actual modal from the modal.js, just put the modal content inside of there.
Change the open modal button to hook into an action creator that sets a prop to open the modal and passes it's content (also set one to close it)
So that looks something like this
<ListGroupItem key={ comment._id }>
<Row>
<Col xs={ 8 } sm={ 10 }>
<FormControl
type="text"
defaultValue={ comment.title }
/>
</Col>
<Col xs={ 4 } sm={ 2 }>
<Button
bsStyle="danger"
className="btn-block">
Remove Comment
</Button>
</Col>
</Row>
<!-- Here is where it changes, -->
<Button
bsStyle="primary"
bsSize="large"
onClick={this.props.openModal(comment)}
>
</Button>
<Modal show={this.props.commentModal} onHide={this.props.closeModal}>
<CommentsModal content={this.props.commentModal} />
</Modal>
Keep in mind, these naming conventions are just for examples sake : use whatever works best for you.
So what happens here is when you click that button you fire this.props.openModal (an action) which does something like this in the reducers -
case actions.OPEN_COMMENT_MODAL:
return state.set('commentModal', action.content);
the close buttons fire the onHide which is linked to the this.props.closeModal action which just does:
case actions.OPEN_COMMENT_MODAL:
return state.set('commentModal', undefined);
So what this allows you to do is have just 1 modal instance and you pass the current comment to it with that button click and open it. The show just checks the truthy value, so you set it back to undefined and it will hide itself.
Then I am passing the prop of content to the modal, so you can then use it inside the modal itself. Again, change the names to whatever works best for you.

Categories