Show warning on leaving without saving - reactjs

I'm working on a React page which has a card component which opens on clicking a button. I'm trying to show a warning if the user tries to close the card without saving the changes. The card doesn't have a close button, it closes when clicking anywhere on the screen outside of the card.
I've built a similar warning modal by checking if the route has changed, however since in this case the card component is part of the same page I cannot apply the same logic.
<CardSidebar
onHide={(e) => this.setState({ showSidebar: false})}
>
<FormComponent
data={this.state.item}
filterTypes={this.state.filterTypes}
dataFields={stores.dataFieldStore.dataFieldsForDropDownComponents}
refresh={this.refreshListHandler}
cancelHandler={this.cancelHandler}
/>
<>
<RouteLeavingGuard
// when={?}
/>
</>
</CardSidebar>

So basically you want to know when the form is dirty and data is not saved so the guard would pop up. As far as I can see you are not using any library for handling this kind of behavior in forms so you need to build custom. I also don't see that you are using something as Redux so you need to lift state up and keep the isDirty value in the state of the component that is shown in the snippet.
//This code goes in the snippet that you pasted
state={
isDirty:false
}
isDirtyHandler(value){
this.setState({isDirty:value})
}
Pass the isDirtyHandler and isDirty as prop for check into the <FormComponent/> and inside the component make the following check
//This goes in <FormComponent/>
componentDidUpdate(prevProps, prevState) {
if(this.state.data !== prevState.data && !this.props.isDirty) {
this.props.isDirtyHandler(true)
}
onSubmitHandler(){
this.props.isDirtyHandler(false)
//whole logic for submitting the form
}
and just in the guard you are checking if form is dirty
<RouteLeavingGuard
popUp={isDirty}
/>

Related

Prevent navigation until React form is validated

I'm trying to implement a validation functionality in React wherein the user will fill in a form, and then the user can navigate to other tabs of the application.
What i want to do is disabling the navigation until certain parts of the form has been filled, if the user attempts to change the tabs without filling said fields, a message will appear directly on the page to alert the user that the fields are required.
My current approach is to capture the event prior to the page switch, check for the form's validity, and then decide whether or not the user can navigate based on said.
The problem is I have no idea which event to capture or how to prevent user's navigation, can someone help me?
EDIT:
Here's what i've tried so far:
componentWillUnmount() {
this.handleValidation();
}
handleValidation = () => {
if (this.invalidForm()) {
this.setState({ showValidation: true });
this.preventNavigation();
}
};
preventNavigation = () => {
window.location.reload();
}
render()
return (
///Form information...
{this.state.showValidation && (
<div>
You must provide all required information
</div>
)}
)
I've thought that by using location.reload in componentWillUnmount(), the page would be reloaded before the navigation (hence, making the user stay in the old page), but what really happens is that it navigates to the other tab, then proceeds to reload there.

How to use a method in the Main component on another

I am trying to save notes into localStorage (or in this case localforage). I have a simple text area with a button called "save." The save button is located in another file indicated below.
I used an example found here to try to set the items.
This is the code I wrote:
SaveMessage() {
var message = <Notepad></Notepad>;
reactLocalforage
.SetItem("Message", message, function(message) {
console.log(message);
})
.catch(function(err) {
console.log(err);
});
}
The var message is something I'm not too sure of either. Notepad is another component with the code which contains the text area and buttons:
<div className="button-container">
<button
className="save-button"
onClick={() => {
props.onSaveMessage(saveMessage);
}}
>
Save
</button>
<button className="remove-button">Show all</button>
</div>
The area where it says onClick I was hoping there would be a way to use the SaveMessage method with localforage initially I tried creating it as a prop (from a tutorial) so in the main method I'd have:
render() {
return (
<div>
<Notepad onSaveMessage={this.SaveMessage}></Notepad>
</div>
);
}
and then on the Notepad component:
<button
className="save-button"
onClick={() => {
props.onSaveMessage();
}}
>
Save
</button>
When I click the save button on my application I am hoping something will be set within the local-storage on the browser, but I get an exception:
TypeError: Cannot call a class as a function
The error occurs when I set item on the save message code above and when I try to call it as a prop onSaveMessage(saveMessage).
You haven't added enough code to be able to fix your exact issue, but I can help you understand how you should proceed to fix it. In React when you want to share information between components, you need to lift that data into a parent component, and then share it through props. If your component tree is profound, then this task can get tedious. So several libraries have been developed to manage your app's state.
I suggest you start by reading "Lifting State Up" from the React docs page. To help you apply these concepts to your current situation, I've created a CodeSandbox in which I try to replicate your situation. You can find it here.
Once you understand the need to "lift" your state, and how you can share both data and actions through props you can migrate to state handler tool. Some of them are:
React Context
Redux
MobX
There are much more. It is not an extensive list, nor the best, just the ones I have used and can vouch that they can help you.
I hope this helps.

Mimic React custom component

I have a custom Reactjs component to display Pagination with next/previous buttons at the bottom of a grid. Now, the business needs to display the same component on top of the grid as well. How to display the previous /next button events based on the input provided in prev/next buttons at the bottom of the grid?
I tried using javascript innerHTML to mimic the behaviour. It works only with the display. It does not attach the event listener of the buttons. I tried even with
document.querySelector.addEventListener('click', ()=>{console.log('test')})
It does not work. Is there a better way to do with react.
I am going to just add some more content to Shmili Breuer answer.
If i understood you correctly you have 2 navigations, one at the top one at the bottom. The way you connect them would be through a state of you component, or a parent component if you are using functional component to render pagination stuff. So if you change the state it will reflect on both of your navigations. Also you can use only one function here, by passing a parameter, im gonna copy a code from before mentioned answer.
// first create a function
nextFunction = (condition) => {
if(condition){
this.setState(prevState=>({
page: prevState.page-1
}))
} else {
this.setState(prevState=>({
page: prevState.page+1
}))
}
}
// then use it in your button
<button onClick={() => this.nextFunction(some condition)}>Next</button>
Just put that component on top and bottom
<Grid>
<Pagination />
{...someOtherComponents}
<Pagination />
</Grid>
it's ok in react. Optimization that you want to do is overhead.
In react you would add an onClick attribute to an element you want to handle a click on.
Something like this
// first create a function
nextFunction = () => {
do next functionality....
}
// then use it in your button
<button onClick={() => this.nextFunction()}>Next</button>
This way you can have both top and bottom pagination call the same function.
Hope this helps

when i use semantic-ui-react Popup trigger, the pre message and current message all show

State of component:
this.state = {
pOM: props.pOM, // the return message of backend, as code is error
isOpen: false
}
<Popup open={this.state.isOpen} trigger={<Button className='btn-gre'>buy</Button>} on='click' content={this.state.pOM} hideOnScroll />
When i click the button of buy, it will send the request to server, and get the return message,Popup will show it. Assume the current message is A, and when i click the button of buy in next time, it returns B message, but the Popup of Component will show all of them, the correct result that i want is just show B. Now i use the setTimeout to avoid this, but not perfect. So please tell me the better solution,thx
Update:
componentWillReceiveProps(nextProps){
this.setState({
pOM: nextProps.pOM
})
}
in componentWillReceiveProps function, i will call setState

React component not updating as expected on state

I am building a simple recipes list app and am running into a problem. I am probably misunderstanding something fundamental about how React renders.
Here's some of the code from my codepen
(scroll down to where I explain my code and my problem)
save(action, item){ //this is the save method of my App component
if (action == 'edit'){ // EDIT
...
} else if (action == 'delete'){ // DELETE
let newRecipes = this.state.recipes.filter( function(recipe){ return recipe.key != item.key } ) //this correctly removes the item I want to delete
this.state.recipes = newRecipes; //making extra sure the state is updated
this.setState({ recipes: newRecipes }); //shouldn't this make App render?
this.forceUpdate(); //cmon App, please re-render
console.log('newRecipes', newRecipes);
console.log('main state', this.state); //the state was updated as expected, but App does not show what the state contains
}
Later, in App's render()
<ul className="well list-group">
{
this.state.recipes.map( (recipe)=>{ //i expect the recipe items generated to reflect App's new state, but they dont :(
return(
<Recipe
save={this.save}
original={recipe}
id={recipe.key}
title={recipe.title}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
)
} )
}
</ul>
My app is like this:
I have an App component at the top that renders all the other components and whose state should contain all the data about the App overall.
state.recipes is an array with recipe objects. These are passed as properties to the Recipe component, in the App render method. (<Recipe title=... />)
The <Recipe /> component has methods and buttons to edit and delete the recipe. when a recipe is edited or deleted, the change is handled by the save() method in App, which is also passed to <Recipe /> as a prop.
From my understanding, that is all standard React usage.
This all seems to work as expected. When I edit or delete a recipe the change is properly reflected in the App state.
The Problem
The unexpected behavior happens when deleting a recipe. Even though the correct recipe is removed from App state, that change is not reflected in the rendered HTML. Instead, whatever the last recipe in the App.state.recipes array is removed and I am left staring at a console log of the state and rendered HTML that show different things.
What I've tried:
I expected that using setState({ recipes: newRecipes}) to update App state to the newly updated recipes list and show it on the screen. But this did not seem to work. I even used .forceUpdate() but that did not work.
I added some console logging to Recipe's constructor function and learned that it only runs once in the beginning. It doesn't run again as I would expect when the recipes array in App's state is changed.
You missed to put a key property on Recipe elements, because of that React does not know which items are changing and how to react to that change. If you just put the key property everything will work as expected.
<Recipe
save={this.save}
original={recipe}
id={recipe.key}
key={recipe.key} // <-- HERE
title={recipe.title}
ingredients={recipe.ingredients}
instructions={recipe.instructions}
/>
Remember to add a key property to all the components created from iterations.
See your example working here
http://codepen.io/damianfabian/pen/egeevR

Resources