I'm trying to create a simple component with React (I'm a new user), and I have some troubles to show and hide div. I'm using a state to handle a click and change state, which works fine. Problem is when I'm using the back button from the browser, and come back on the main page, I've no clue how to handle state change as there is no interaction with the user.
I tried to use the location context to change state if the URL pathname === "/", but it looks like anti react pattern because I have to force the component to rerender and check the pathname inside the initial state function. Any ideas how to handle this case?
// DIV on the main page
const Div = React.createClass({
/*contextTypes: {
location: React.PropTypes.object
},*/
getInitialState: function() {
console.log("get initial state");
return { hideDiv: false };
},
handleClick(){
this.setState({ hideDiv: true });
},
render() {
console.log(this.state.hideDiv);
let componentDOM;
if(this.state.hideDiv === true){ componentDOM = <div></div>;}
else{
componentDOM = <div id='showHide'>
<form>
<div>
<select>
<option> ... </option>
</select>
</div>
//Link to a second page
<button type='submit' onClick={this.handleClick}> <Link to {'/destination'}>Submit</Link></button>
</form>
</div>;
}
return (componentDOM);
}
});
I would advise against storing the information about whether or not the component with the form is visible in its own state. From your description, it seems to me like this information belongs higher in the hierarchy - the Div component itself is not capable of deciding whether or not it should be visible, as that depends on some context (URL / application phase) unknown to it.
I'd recommend something like this:
var App = React.createClass({
//Data for the form, you might want to keep them in a store
getInitialState(){ return {data: {}}; }
render(){
//Pass data from your routing library to the props of App
if(this.props.routingParams.url === 'form')
return <Div data={this.state.data} onDataChanged={...} />
else
return <Destination data={this.state.data} />
}
});
Plus remove the state and the hiding logic from Div completely.
To do this you should save your state, using localstorage for example, like this:
handleClick: function(e) {
this.setState({hideDiv: true});
var state = this.state; // we need to add hideDiv with new value because setState could not update instantly
state.hideDiv = true;
localStorage.setItem('MyDivComponent', JSON.stringify(state));
}
And then, when a component mount, get default state:
getInitialState: function() {
var state = JSON.parse(localStorage.getItem('MyDivComponent')) || {};
return {
hideDiv: state.hideDiv || false
};
}
Related
I'm using React js. I need to detect page refresh. When user hits refresh icon or press F5, I need to find out the event.
I tried with stackoverflow post by using javascript functions
I used javascript function beforeunload still no luck.
onUnload(event) {
alert('page Refreshed')
}
componentDidMount() {
window.addEventListener("beforeunload", this.onUnload)
}
componentWillUnmount() {
window.removeEventListener("beforeunload", this.onUnload)
}
here I have full code on stackblitz
If you're using React Hook, UseEffect you can put the below changes in your component. It worked for me
useEffect(() => {
window.addEventListener("beforeunload", alertUser);
return () => {
window.removeEventListener("beforeunload", alertUser);
};
}, []);
const alertUser = (e) => {
e.preventDefault();
e.returnValue = "";
};
Place this in the constructor:
if (window.performance) {
if (performance.navigation.type == 1) {
alert( "This page is reloaded" );
} else {
alert( "This page is not reloaded");
}
}
It will work, please see this example on stackblitz.
It is actually quite straightforward, this will add the default alert whenever you reload your page.
In this answer you will find:
Default usage
Alert with validation
1. Default Usage
Functional Component
useEffect(() => {
window.onbeforeunload = function() {
return true;
};
return () => {
window.onbeforeunload = null;
};
}, []);
Class Component
componentDidMount(){
window.onbeforeunload = function() {
return true;
};
}
componentDidUnmount(){
window.onbeforeunload = null;
}
2. Alert with validation
You can put validation to only add alert whenever the condition is true.
Functional Component
useEffect(() => {
if (condition) {
window.onbeforeunload = function() {
return true;
};
}
return () => {
window.onbeforeunload = null;
};
}, [condition]);
Class Component
componentDidMount(){
if (condition) {
window.onbeforeunload = function() {
return true;
};
}
}
componentDidUnmount(){
window.onbeforeunload = null;
}
Your code seems to be working just fine, your alert won't work because you aren't stopping the refresh. If you console.log('hello') the output is shown.
UPDATE ---
This should stop the user refreshing but it depends on what you want to happen.
componentDidMount() {
window.onbeforeunload = function() {
this.onUnload();
return "";
}.bind(this);
}
Unfortunately currently accepted answer cannot be more considered as acceptable since performance.navigation.type is deprecated
The newest API for that is experimental ATM.
As a workaround I can only suggest to save some value in redux (or whatever you use) store to indicate state after reload and on first route change update it to indicate that route was changed not because of refresh.
If you are using either REDUX or CONTEXT API then its quite easy. You can check the REDUX or CONTEXT state variables. When the user refreshes the page it reset the CONTEXT or REDUX state and you have to set them manually again. So if they are not set or equal to the initial value which you have given then you can assume that the page is refreshed.
Currently I have a navbar component that stays at the top for all the pages except the home page. To achieve this I used conditional rendering using useEffect and useState where I check if the current URL is the home page or not.
Code:
const [ishome,setIsHome] = useState(false);
useEffect(function onFirstMount() {
function onLoad() {
const url= window.location.href;
if(url === "http://localhost:3000/home"){
setIsHome(true)
}
else{
setIsHome(false)
}
}
window.addEventListener("load", onLoad);
}, []);
return (
<div className="fullNav" style={{marginTop:ishome?"100vh":""}}>
But the problem with this is that everytime this page loads I can see the navbar at the top of the home page for a split second and then it goes down. I dont want it to be shown at the top of the homepage everytime the user clicks refresh.
You are checking your location in useEffect(with an empty dependency array). This essentially means you are checking it in componentDidMount. So that is after the first render. Probably that is why your styling is applied later.
If you want it to happen as soon as it is rendered, why not call the function while setting state.
Something like this:
export default function App() {
const [ishome,setIsHome] = useState(function onLoad() {
const url= window.location.href;
if(url.indexOf("x")){
return 'true';
}
else{
return 'false';
}
});
useEffect(() => {
}, []);
return (
<button>{ishome}</button>
)
}
Sandbox
Let's get right to the code to demonstrate:
function App() {
let message = '!!message init value, this should not be displayed!!';
const busyState = React.useState(true);
let isBusy = busyState[0];
let setIsBusy = busyState[1];
React.useEffect(() => {
log('didMount');
getLatestMessage();
}, []);
function log(message) {
document.getElementById('console').innerText = message;
}
function getLatestMessage() {
log('getting latest message...');
setTimeout(() => {
log('http call complete, there was a message for you');
message = 'Hello World'
setIsBusy(false);
}, 3000);
}
return (
<div>
{
isBusy ?
<h4>
Busy Getting Data
</h4> :
<h4>
{message}
</h4>
}
</div>
);
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<label>Console:</label>
<h4 id="console" style="color: red">
</h4>
I am using state here. I am using the isBusy state variable to cause another render of my component. But message remains it's initialized value. I know I could use the useState hook for the message variable as well to fixed this but I thought the isBusy state flag would be enough to "flush" the old ReactJS functional component UI...?
Everytime a re-render is triggered your Functional Component, which is just a function, is called. The value of the variable "message" is set to it's "initial value" on every render in the top line of your function definition. So the value will never change. To persist state during multiple render-cycles you need to use state.
You can trigger a re-render if you add the isBusy variable as a dependency to your useEffect() hook.
React.useEffect(() => {
getLatestMessage();
}, [isBusy]);
Whenever dependiences change in useEffect(), it will trigger a re-render. Using an empty array causes it to only fire on initial render.
Message needs to be a reference.
const message = useRef('initial value');
...
message.current = 'Hello World'
...
<h4>
{message.current}
</h4>
EDIT:
Just hoist you message variable outside of app()
(Move it up a few lines, outside of the component)
I'm new to ReactJS and one thing that I'm working on is Pagination. ReactJS is a great tool to make complex UI views but sometimes the simplest thing is the hardest. I could do it in jQuery but I want to do it using React.
Basically, I would like to have a "Load More" style of pagination where items are appended to the body (like a news feed). In my code, I do an ajax request to get the data, put it in a state variable, and then render the DOM. I have successfully done this with only 1 page of data because I created a new variable for items.
onCallSuccessGetListings: function(data) {
this.setState({ hasSuccess: data.success });
if(data.success !== false) {
this.setState({
hasSuccess: JSON.parse(data).success,
newListings: false
});
if(this.props.hasClickedPagination) {
this.setState({
newListings: JSON.parse(data).result
});
} else {
this.setState({
listings: JSON.parse(data).result
});
}
}
},
Basically, I know when the person clicked on "Load More" and then I use a new variable to store the data. Here's the render function:
render: function() {
var markup = [],
nextMarkup = [];
if(this.state.hasSuccess) {
markup = Object.keys(this.state.listings).map(function(value){
return (
<div className="property__item" key={value}>
blablabla
</div>
)
}, this)
if(this.state.newListings !== false) {
nextMarkup = Object.keys(this.state.newListings).map(function(value){
return (
<div className="property__item" key={value}>
blablabla
</div>
)
}, this)
}
} else {
markup.push(<p key="1">No results</p>)
}
return (
<div className="property__list--grid">
{ markup }
{ nextMarkup }
</div>
);
}
As you can see, I'm duplicating the code and I couldn't find a way to do it the "React" style. I don't want to create 10 variables when the user clicks on "Load More" 10 more times.
If anyone could help, that would be great. I can't think of a solution and it has been haunting me for days.
Many thanks.
This tutorial might help everyone to implement a paginated list in React. It has the list itself and a More button to fetch the next subset of a paginated list from a backend. The backend in tutorial is already provided. I hope it helps anyone who wants to implement such a functionality.
There is no need to create a new state variable per "page of results". It looks to me that you are doing an infinite scroll result set and that the onCallSuccessGetListings lives within your Component.
Instead of creating a new variable for a new page of results, append any new results to the existing variable. e.g.
onCallSuccessGetListings: function(data) {
var jsonData = JSON.parse(data);
this.setState({
hasSuccess: jsonData.success,
listings: jsonData.success
? this.state.listings.concat(jsonData.result)
: this.state.listings
});
},
Make sure you set your initial state to be { listings = [] } (https://facebook.github.io/react/docs/component-specs.html#getinitialstate).
The above code will trigger an update on your component every time the state is set, then you can simply loop over the results. I would also consider updating the way you use the state.hasSuccess flag. I think it should rather be used to indicate if an error occurred. Here is a revised render() method:
render: function() {
var markup = [];
hasSuccess = this.state.hasSuccess,
listings = this.state.listings;
if(!hasSuccess) {
markup.push(<div>An error occurred.</div>);
} else if (listings.length > 0) {
markup = Object.keys(listings).map(function(value){
return (
<div className="property__item" key={value}>
blablabla
</div>
)
}, this)
} else {
markup.push(<p key="1">No results</p>)
}
return (
<div className="property__list--grid">
{ markup }
</div>
);
}
I am working with React and I am doing a chat app. As you now, everytime there is a new message or any event in the chat screen, that last event/item/message should be focus so the user doesn't have to scroll down in order to look for the new event in the chat. As a regular chat app.
Every event is attached to this.props.chatMessages.
I already accomplished this behavior but only when the user adds a new message. And I need that functionality for everything, sometimes the chat says
New user was added to the chat
or
User XXXXX leaved the chat
so, those are different events, those are informative messages and not regular user messages.
As I mentioned before, every new event is attached to this.props.chatMessages
this is what I did in order to focus in the last message when the user sends a message by himself
constructor (props) {
super(props);
this.addMessage = this.addMessage.bind(this);
this.focusOnLastMessage = this.focusOnLastMessage.bind(this);
ChatActions.connect({ socketUrl : this.props.socket, mode : this.props.mode, room : this.props.room, user : this.props.user });
}
addMessage (text) {
if (text.length) {
ChatActions.addMessage(text);
this.focusOnLastMessage();
}
}
focusOnLastMessage () {
console.log(this.props.chatMessages);
let lastMessage = React.findDOMNode(this.refs.messages);
lastMessage.scrollTop = lastMessage.scrollHeight;
}
and in the render method I have something like this
chatForm = <ChatForm onAddMessage={this.addMessage} />;
here is the full function just in case. Where you see <ChatItem .../> is because that is the component to visualize every new event happening in the chat.
class ChatView extends React.Component {
static getStores () {
return [ ChatStore ];
}
static getPropsFromStores () {
return ChatStore.getState();
}
constructor (props) {
super(props);
this.addMessage = this.addMessage.bind(this);
this.focusOnLastMessage = this.focusOnLastMessage.bind(this);
ChatActions.connect({ socketUrl : this.props.socket, mode : this.props.mode, room : this.props.room, user : this.props.user });
}
addMessage (text) {
if (text.length) {
ChatActions.addMessage(text);
this.focusOnLastMessage();
}
}
focusOnLastMessage () {
console.log(this.props.chatMessages);
let lastMessage = React.findDOMNode(this.refs.messages);
lastMessage.scrollTop = lastMessage.scrollHeight;
}
render () {
let messages = this.props.chatMessages.map((message) => {
return <ChatItem info={message.info} me={message.me} player={message.player} message={message.message}
onNewEvent={this.focusOnLastMessage} />;
}), chatForm, hr, dealerPlayerMessages, dealerPlayerBox, minusPlusButtons;
if (this.props.mode === 'player') {
dealerPlayerMessages = <ul ref="messages">{messages}</ul>;
hr = <hr />;
chatForm = <ChatForm onAddMessage={this.addMessage} />;
dealerPlayerBox = <div>{dealerPlayerMessages}{hr}{chatForm}</div>
}
if (this.props.mode === 'dealer') {
minusPlusButtons = <MinusPlusButtons />
dealerPlayerMessages = <ul ref="messages">{messages}</ul>;
dealerPlayerBox = <div> {minusPlusButtons} {dealerPlayerMessages}</div>
}
return <div>
{dealerPlayerBox}
</div>;
}
}
so, what should I do in order to listen to every change in this.props.chatMessages in order to focus on every new item in the chat ?
I'm just guessing here, but let's say a new person joins the chat updating this.props.chatMessages to contain a new message notifying users about this change. This means that the first lifecycle method is going to fire
componentWillReceiveProps (nextProps) {
// do something with new message
}
But you need to scroll the message after this has been painted to the dom, so luckily there's a life cycle method for that too.
componentDidUpdate (prevProps, prevState) {
// dom has been updated with new message, scroll your screen!
this.focusOnLastMessage()
}
Edit: You may need to bind this in your constructor to use this, but I don't remember. Not all lifecycle methods need it.
Lifecycle Methods in docs