After click on delete button i want to make the selectedPost to null so that user gets a prompt to select post again. I am updating the state onDeleteClick(). But everytime i am updating, componentDidMount is getting called and update the data of selectedPost.
Is there any other way to make the state to null so that user get the specific prompt?
class FullPost extends Component {
state = {
selectedPost: null,
postVisible: false
}
componentDidUpdate() {
if (this.props.id) {
if (!this.state.selectedPost || (this.state.selectedPost && this.state.selectedPost.id !== this.props.id)) {
axios.get('https://jsonplaceholder.typicode.com/posts/' + this.props.id)
.then(response => {
this.setState({ selectedPost: response.data })
}
);
}
}
}
onDeleteClick = () => {
this.props.deletePost(this.state.selectedPost.id);
this.setState({ selectedPost: null });
console.log(this.state.selectedPost);
}
render() {
let post = <p style={{ textAlign: 'center' }}>Please select a Post!</p>;
if (this.state.selectedPost) {
post = (
<div className="FullPost">
<h1>{this.state.selectedPost.title}</h1>
<p>Content</p>
<div className="Edit">
<button className="Delete"
onClick={this.onDeleteClick}>
Delete</button>
</div>
</div>
);
}
return post;
}
}
Related
I have a React component called PopUpBanner that I use to show messages. For example, in my login component, I use it like this. If an error occurs, then I set the bannerMessage state to have text so that the banner shows:
this.setState({
bannerMessage: {
msg: error.message + ". Incorrect email address or password.",
isError: true,
},
});
Here is how the component is then used:
<PopUpBanner
message={bannerMessage.msg}
isError={bannerMessage.isError}
></PopUpBanner>
And here is the PopUpBanner class:
import React, { Component } from "react";
class PopUpBanner extends Component {
constructor(props) {
super(props);
this.state = {
message: this.props.message,
};
}
// TODO : not in use
reset = () => {
this.resetId = setTimeout(
function () {
this.setState({ message: "" });
}.bind(this),
3000
);
};
componentDidMount() {}
componentWillUnmount() {
if (this.timeoutId) {
clearTimeout(this.timeoutId);
console.log("clearing time out");
}
}
render() {
const message = this.props.message;
const isError = this.props.isError;
return (
<div style={message != "" ? { display: "block" } : { display: "none" }}>
<div>
{isError ? (
<div
className="alert alert-danger text-center"
role="alert"
style={{ width: "50%", margin: "auto" }}
>
{message}
</div>
) : (
<div
className="alert alert-primary text-center"
role="alert"
style={{ width: "50%", margin: "auto" }}
>
{message}
</div>
)}
</div>
</div>
);
}
}
export default PopUpBanner;
The problem is that the PopUpBanner is shown until the page is refreshed or navigated to another page.
So if you look at the PopUpBanner class, I attempted to use setTimeout but wasn't able to finish it.
Any ideas on how I can transform PopUpBanner component to be on a timer?
I see two options:
Handle it in the parent component, only rendering PopUpBanner when it should be there, using setTimeout to trigger a state update that re-renders the parent without rendering PopUpBanner.
Handle it in PopUpBanner, returning null from render after the expiration.
I would prefer #1 over #2. But your existing code is basically doing #2, you just have to adjust render to support it:
render() {
const message = this.props.message;
if (!message) {
return null;
}
// ...show the message...
But as discussed in teh comments, I wouldn't copy props to state like that. So instead:
constructor(props) {
super(props);
this.state = {
expiredMessage: null,
};
}
then to expire a message:
setupExpiration() {
this.expirationTimer = setTimeout(() => {
this.setState(() => ({expiredMessage: this.props.message}));
}, 1000); // <== Or however long you want it showing
}
...which you call from a couple of lifecycle methods:
componentDidMount() {
this.setupExpiration();
}
componentDidUpdate() {
this.setupExpiration();
}
and render becomes:
render() {
const { expiredMessage } = this.state;
const { message } = this.props;
if (expiredMessage === message) {
return null;
}
// ...show the message...
But again, I'd go for having the parent in control of this, actually removing PopUpBanner when it shouldn't be showing:
class PopUpBanner extends React.Component {
render() {
const {message} = this.props;
return <div className="banner">{message}</div>;
}
}
class Parent extends React.Component {
state = {
message: null,
};
constructor(props) {
super(props);
this.showMessage = this.showMessage.bind(this);
this.messageTimer = 0;
}
showMessage() {
clearTimeout(this.messageTimer);
this.setState({message: "Hi there, I'm a banner"});
this.messageTimer = setTimeout(() => {
this.setState({message: null});
}, 1000);
}
render() {
const {message} = this.state;
const {showMessage} = this;
return <div className="with-banner">
{message && <PopUpBanner message={message} />}
<div>
<input type="button" value="Show Message" onClick={showMessage} />
</div>
</div>;
}
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
I have 10 records in the database that i want to load dynamically.
This app loads data from database using react redux. The Load more button also works.
Here is my problem,
Each time I click on Load More button, it will load more 2 records from the
database which will replace already displayed records.
I think that my problem lies is the Loadmore() functions
1.)how do I append the new records to already displayed records each time the loadmore button is click.
2.)Am also checking to display a message No more records once data is finished but cannot get it to work properly as the message
got displayed each time loadmore button is clicked
import React from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { userActions } from "../_actions";
class HomePage extends React.Component {
constructor(props) {
super(props);
this.state = {
row_pass: 0
};
this.row = 0;
this.rowperpage = 2;
this.buttonText = "Load More";
this.loadMore = this.loadMore.bind(this);
}
componentDidMount() {
this.props.dispatch(userActions.getAll(this.row));
}
loadMore() {
this.row += this.rowperpage;
alert("loading" + this.row);
this.props.dispatch(userActions.getAll(this.row));
this.buttonText = "Load More";
}
get finished() {
if (this.row >= this.rowperpage) {
return <li key={"done"}>No More Message to Load.</li>;
}
return null;
}
render() {
const { user, users } = this.props;
return (
<div
style={{ background: "red" }}
className="well col-md-6 col-md-offset-3"
>
<h1>
Hi{user.message}! {user.token}
</h1>
<p>You're logged in with React!!</p>
<h3>All registered users:</h3>
{users.loading && <em>Loading users...</em>}
{users.error && (
<span className="text-danger">ERROR: {users.error}</span>
)}
{users.items && (
<ul>
{users.items.map((user, index) => (
<li key={user.id}>
{user.firstName + " " + user.lastName}:
<span>
{" "}
- <a>home</a>
</span>
</li>
))}
{this.finished}
</ul>
)}
<p>
<a className="pic" onClick={this.loadMore}>
{this.buttonText}
</a>
<input
type="text"
className="form-control"
name="this.row"
id="this.row"
value={this.row}
onChange={this.handleChange}
/>
</p>
</div>
);
}
}
function mapStateToProps(state) {
const { users, authentication } = state;
const { user } = authentication;
return {
user,
users
};
}
const connectedHomePage = connect(mapStateToProps)(HomePage);
export { connectedHomePage as HomePage };
here is user.action.js
function getAll(row) {
return dispatch => {
dispatch(request(row));
userService.getAll(row)
.then(
users => dispatch(success(users)),
error => dispatch(failure(error.toString()))
);
};
user.reducer.js code
import { userConstants } from '../_constants';
export function users(state = {}, action) {
switch (action.type) {
case userConstants.GETALL_REQUEST:
return {
loading: true
};
case userConstants.GETALL_SUCCESS:
return {
items: action.users
};
case userConstants.GETALL_FAILURE:
return {
error: action.error
};
/*
case userConstants.DELETE_FAILURE:
// remove 'deleting:true' property and add 'deleteError:[error]' property to user
return {
...state,
items: state.items.map(user => {
if (user.id === action.id) {
// make copy of user without 'deleting:true' property
const { deleting, ...userCopy } = user;
// return copy of user with 'deleteError:[error]' property
return { ...userCopy, deleteError: action.error };
}
return user;
})
};
*/
default:
return state
}
}
If I understand you right, this is what you need to do. Firstly, don't replace the whole items with action.users. Concat it with the old items state instead:
case userConstants.GETALL_REQUEST:
return {
...state,
loading: true
};
case userConstants.GETALL_SUCCESS:
return {
loading: false,
error: null,
items: [ ...(state.items || []), ...action.users ]
};
From here, to properly show "No More Message to Load", you need to fix this.finished condition:
get finished() {
if (this.row >= 10) {
return (<li key={'done'}>No More Message to Load.</li>);
}
return null;
}
Where 10 is the total count of users, not this.rowperpage. Ideally, this value should come from API response.
Hope this helps.
UPDATE
To display proper buttonText I would suggest to replace your current implementation with something like:
get buttonText() {
if (this.props.users.loading) return 'Loading...';
if (this.props.users.error) return 'Error has occurred :(';
return 'Load more'
}
I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}
I have SearchBar Component like so:
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
page: 1,
prevButton: false,
nextButton: true,
};
And buttons like so:
<div>
<button
className="btn btn-secondary"
onClick={this.handlePrev}
disabled={!this.state.prevButton}
>
Prev Page
</button>
<span className="SearchBar-page-numbers">{this.state.page}</span>
<button
className="btn btn-secondary"
onClick={this.handleNext}
disabled={!this.state.nextButton}
>
Next Page
</button>
</div>
Now I want to add code that for every component update will check which page on the user is.
-
So if the user is on page number one (this.state.page === 1) this.state.prevButton should be always false, but for every other page this.state.prevButton should be always true.
this.state.nextButton should be false only when this.state.page === 10
I need navigation between page 1 to 10 only.
-
Which React Lifecycle Methods would be sufficient for that functionality?
I tried something like that but it is no good, it is unclear, messy and doesn't work...
componentDidUpdate(prevProps) {
if (this.props !== prevProps) {
if (this.state.page === 1) {
this.setState({ prevButton: false });
}
if (this.state.page !== 1) {
this.setState({ prevButton: true });
}
}
}
UPDATE:
If you see better way of doing this, please share you thoughts!
Full code of that component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import {
wakeUpHerokuServerFromSleep,
fetchRecipesAndPage,
loadRecipes,
showClickedInfo,
addLocalStorageToFavoritesList,
} from '../../actions/';
import './style.css';
import ButtonSearch from '../../components/ButtonSearch';
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
term: '',
page: 1,
prevButton: false,
nextButton: true,
};
this.handlePrev = this.handlePrev.bind(this);
this.handleNext = this.handleNext.bind(this);
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
componentDidMount() {
this.props.wakeUpHerokuServerFromSleep();
const localStorageData = JSON.parse(
localStorage.getItem('lastSavedFavourites')
);
if (localStorageData) {
this.props.addLocalStorageToFavoritesList(localStorageData);
}
}
componentDidUpdate(prevProps, prevState) {
if (this.props !== prevProps) {
this.setState({ term: this.props.showClickedInfoFromStore });
this.checker(this.props);
}
const { page } = this.state;
if (prevState.page !== page) {
this.setState({ prevButton: page !== 1 });
}
}
// If some ingredient was manually selected
// go to page 1 of that ingredient
checker(properties) {
if (properties.manualSelectionFromStore) {
this.setState({ page: 1 });
}
}
// If input was changed go to page 1
handleInputChange(event) {
this.setState({ page: 1 });
this.setState({ term: event.target.value });
}
// After submit, go to page 1 and fetch data
handleSubmit(event) {
this.setState({ page: 1 });
if (this.state.term === '') {
event.preventDefault();
this.props.showClickedInfo('');
} else {
event.preventDefault();
this.props.fetchRecipesAndPage(this.state.term, 1);
this.props.showClickedInfo(this.state.term);
}
}
handlePrev() {
let newPage = this.state.page - 1;
if (newPage <= 0) {
newPage = 1;
}
this.setState({ page: newPage });
this.props.loadRecipes(newPage);
}
handleNext() {
let newPage = this.state.page + 1;
if (newPage >= 10) {
newPage = 10;
}
this.setState({ page: newPage });
this.props.loadRecipes(newPage);
}
buttonsView() {
// Show navigation buttons (prev, next):
// If there is an error coming from server
// OR
// If current search isn't null AND app has found some data and successfully fetched it
if (
this.props.error ||
(this.props.currentSearchFromStore !== null &&
this.props.checkIfSomeDataWasFound)
) {
return (
<div>
<button
className="btn btn-secondary"
onClick={this.handlePrev}
disabled={!this.state.prevButton}
>
Prev Page
</button>
<span className="SearchBar-page-numbers">{this.state.page}</span>
<button
className="btn btn-secondary"
onClick={this.handleNext}
disabled={!this.state.nextButton}
>
Next Page
</button>
</div>
);
}
// Esle return just <div />
return <div />;
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit} className="SearchBar-input-group">
<input
className="form-control"
placeholder={this.props.showClickedInfoFromStore}
value={this.state.term}
onChange={this.handleInputChange}
/>
<ButtonSearch className="btn btn-secondary submit">
Search
</ButtonSearch>
</form>
<div className="SearchBar-pagination-buttonsView">
{this.buttonsView()}
</div>
</div>
);
}
}
function mapStateToProps(state) {
return {
error: state.error,
currentSearchFromStore: state.currentSearchTerm,
checkIfSomeDataWasFound: state.checkRecipesData,
showClickedInfoFromStore: state.showClickedInfo,
manualSelectionFromStore: state.manualSelection,
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(
{
wakeUpHerokuServerFromSleep,
fetchRecipesAndPage,
loadRecipes,
showClickedInfo,
addLocalStorageToFavoritesList,
},
dispatch
);
}
export default connect(mapStateToProps, mapDispatchToProps)(SearchBar);
Alternately, you could just disable the buttons based on page as follows:
const { page } = this.state;
<div>
<button
className="btn btn-secondary"
onClick={this.handlePrev}
disabled={page === 1}
>
Prev Page
</button>
<span className="SearchBar-page-numbers">{this.state.page}</span>
<button
className="btn btn-secondary"
onClick={this.handleNext}
disabled={page === x}
>
Next Page
</button>
</div>
I'm not sure how you are disabling the next button, but subsitute x above with the number of pages.
Regarding your 'which lifecycle method' question, I would suggest using componentWillReceiveProps if you're checking whether something should re-render based on props changes. You'd compare this.props to nextProps (pass in nextProps as the argument to componentWillReceiveProps) in that method, and perform an action based on that.
However, if you're just trying to determine whether to enable/disable buttons, you could use the following in the 'disabled' property of the buttons.
i.e. for 'previous' button
disabled={this.state.page === 1}
and for 'next' button
disabled={this.state.page === 10}
I'm creating a react application, and I have a component that is define more or less like this:
import React, { Component } from 'react';
import axios from 'axios';
import './App.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
data: [],
loading: true,
error: null
};
}
componentDidMount() {
var _this = this;
this.serverRequest =
axios
.get("LinkToAPI")
.then(result => {
_this.setState({
data: result.data,
loading: false,
error: null
});
})
.catch(err => {
_this.setState({
loading: false,
error: err
});
});
}
componentWillUnmount() {
this.serverRequest.abort();
}
renderLoading() {
return <div>Loading...</div>
}
renderError() {
return (
<div>
Something when wrong: {this.state.error.message}
</div>
);
}
renderData() {
const { error, data} = this.state;
if (error) {
return this.renderError();
}
return (
<div>
{data.map(d=> {
if (d.imageUrl) {
<div className="dataDiv" style="background: url('{d.imageUrl}')" key={d.Id}>{d.name}</div>
} else {
<div className="dataDiv" style="background: url('LinkToSomeImage')" key={d.Id}>{d.name}</div>
}
}
)}
</div>
)
}
render() {
return (
<div className="App">
{this.props.loading ? this.renderLoading() : this.renderData()}
</div>
);
}
}
It basically gets the JSON data from the API, and using it renders some divs with the data inside the JSON. I'm applying to the divs containing the data dataDiv class, which is define inside my App.css file. Additionally, I want to set a background image for the div. What exactly I want to do is that if the data entry includes a field named imageUrl I want to use that url as a url to the background image, otherwise, if it is null or empty, I want to use a default url that I found from the internet. What is a proper way to handle this in React? The code segment above doesn't seem to work, especially the if-else statement inside the renderData function. How can I fix this code, or is there any way to handle this more gracefully, probably maybe inside the CSS?
I would do like this
Please make sure to check backgroundUrl equal to your desired CSS.
{data.map(d => {
let backgroundUrl = "LinkToSomeImage";
if (d.imageUrl) {
backgroundUrl = d.imageUrl;
}
return (
<div className="dataDiv" style={{backgroundImage: `url(${backgroundUrl})`}} key={d.Id}>{d.name}</div>
)
})}
EDIT
A full function would be:
renderData() {
const { error, data} = this.state;
if (error) {
return this.renderError();
}
return (
<div>
{data.map(d => {
let backgroundUrl = "LinkToSomeImage";
if (d.imageUrl) {
backgroundUrl = d.imageUrl;
}
return (
<div className="dataDiv" style={{backgroundImage: `url(${backgroundUrl})`}} key={d.Id}>{d.name}</div>
)
})}
</div>
)
}
<Box
onClick={() => {
history.push({
pathname: `/p/c/${data.ProductName.replace(/\//g, "~")}/1`
});
}}
css={{
backgroundImage:`url(${data.imageUrl||"/default-placeholder.png"})`,
backgroundPosition: 'center',
backgroundRepeat: 'no-repeat'
}}
>