React state updating unexpected - reactjs

I'm trying to make a form with functionality where the user first views a list of days containing specific info about that day, can click a day, and then a unique form to edit info about that day appears as a modal. So the list is showing data from state, and the form shows that state as a defaultValue of inputs in the form. The form should have the functionality to save changes and to also cancel, which should close the form and forget any changes the user may have made.
The problem is that the form is displaying a value from state but when I press cancel, the changes I was in the middle of making still get sent to state, thereby updating the view of the list of days.
As a user I should expect to see the original data before I started editing, but what I'm seeing are the edits that I was making when I changed my mind.
here is a codeSandbox recreating the issue...
codeSandbox recreating issue
This seems like very basic functionality, but yet I can't seem to find any information on it. Am I missing some basic React concept here?

Props are passed by-ref in React. In your example, you're passing the days array all the way through to Form.js. On line 30 of Forms.js, you take the new value of the input field and store it in days[...].shifts[...].message, which changes the value in the original state object of the topmost component. Because this is done by mutating the state object and not by setState, the changes aren't seen until something else triggers a re-render of the topmost component, at which point the new values are shown in the button list.
One solution would be to store a temporary message state variable in the Form component and if the user clicks Submit, pass that current value back up as a parameter to this.props.onSubmit and let the top-level component use that value to update the days array properly (with a true state change as necessary).
Working example is here: https://codesandbox.io/s/9oqxlwol4o

Related

Why is useState failing to read initialization value?

I have a modal that includes a form for editing data about cats. I am passing into that modal the current value of the cat's name along with other attributes in an object called selectedCatData. By using console.log I have verified that selectedCatData and the name field are both being passed into the modal component as props correctly. When I try using the name as an initialization value in a call to useState it does not set the value and subsequently printing the local state variable "formName" results in undefined.
function EditModal(props) {
let { selectedCatData } = props
console.log(`EditModal recieved props "name": ${selectedCatData.name}`)
let [formName, setFormName] = useState(selectedCatData.name)
console.log(`EditModal copied name to local state: ${formName}`)
The first console.log correctly prints "Kitty"
The second consol.log prints "undefined" and this has me vary confused.
I have used useState a lot before and never run into a problem like this.
The saved values exist as state at a height level in my component tree. I want to make a local copy that the form will use to track unsaved changes.
Thanks in advance for any help!
I got this working and, though I never figured out for sure why the code above did not work, I have a guess.
Here's how I got it working:
I switched from the repetitive structure used in the question (that breaks apart selected cat data into several calls to useState) to one that keeps the state as one object. I passed that down one more layer in the component tree to a and did pretty much the same thing there, setting state like this:
const [formState, setFormState] = useState({...selectedCatData})
Here's my guess as to why that worked:
The Edit Modal described above never unmounts. It is always present in the DOM, it just has its display property set to "none" when its "open" prop is false. By contrast, the is a child of that modal and is therefore removed from the DOM when its parent is hidden with display: "none".
Because this form is being completely removed and a new React Component created the call to useState that initializes the Form's local state is made each time the Form opens. When that state was situated in the Edit Modal useState was only being called to initialize the local state one time on the first load of the page.
That, again, is just my best guess as to why moving the state down one layer in the component tree solved my issue. If anyone reading this can confirm or refute that guess I'd love to hear it.

Redux Form, values disappear on re-render

I have a form, in this form a user is able to attach files. When the user removes a file, the state changes and the form re-renders. When this happens, all the values the user has entered disappear.
Is there a way to keep the values in the fields upon re-render?
I'm using redux-forms.
You should be able to pass the form "initial values". So, as the form is updated, you should be updating whatever holds your initial values in state. Then, once they remove a file, you can pass the initial values back in (what they previously entered) so that even if the form re-renders the values should persist.
https://redux-form.com/7.2.2/examples/initializefromstate/

Redux-form: keepDirtyOnReinitialize causes form to retain values also after unmount

Using redux-form here. Part of the form is inited asyncronously after an ajax request (a list of items in a select) that depends on what the user selected on a separate field.
To achieve this I put the new items in the initialValues and I enableReinitialize: true. The select pre-fills properly and all is good.
But since I want to retain the other values that the user might have added in the form, I also keepDirtyOnReinitialize: true.
So far so good.
The problem is that if the user navigates back to another page (without submitting the form) I would like to clear the all form and start from scratch.
It seems that the form is correctly destroyed on unmount but when we navigate back to the form again, the previous values are retained because of the keepDirty...
Should the unmounting of the form beat the keepDirty? Otherwise is it the only option to manually cleanup the form before navigating back to the other page or am I missing a simpler way?
Problem was the 'dirty' values are actually taken from redux store so it is not the form retaining them but it is me retaining them and feeding them again to redux-form in the initial values. I need to rethink a bit my code.

Where to store previous values in ReactJS

I have a React component that represents a list of pages (used when paging through search results). For example, When I click on page 3, and then click a search result, a new page appears, and the component is destroyed. When I go back, it recreates the component, and the selected page is reset to 1. I want to keep the last selected page value, and default the selected page to that when the component is recreated. Where can I store a 'previouslySelectedPage' value, and how can I access it? (I'm a noob at React, do I have to store it in the state/props of a parent element that does not get destroyed when I click a search result, so that old values can be retained?)
One option is to store in the state of a parent that doesn't get unmounted and re-mounted. And then you can pass those state values as props to the child component.
The other option is to use something like redux to create data stores in your application. Adding redux to your app will not be a trivial undertaking but may provide some good value long term if your app is complex and will need to scale.

Text input with default value in React/ Redux

In my React/ Redux app, I have a text input with its default value retrieved via Ajax call. User can edit the default value in the input, and then submit the updated value by clicking on a 'submit' link. I'm struggling with using either uncontrolled or controlled inputs for this.
Using uncontrolled input with defaultValue: the component doesn't get re-rendered when the data for default value comes back from initial Ajax call (detailed in official document here). So the input field is blank all the time.
Using controlled input with value bound to component's props: This does give the default value correctly, however since I can't change the props, the field is basically "locked". I can get around this by triggering an action to modify the global state in onChange handler and force the whole component re-rendering, but this again poses other issues. For one it seems excessive to do so in onChange, and also I don't want to commit to changing the state before user clicking the submit link.
Any suggestions what I can do here?
As the docs say, the defaultValue is only useful at the initial render. So if you want to use defaultValue you have to delay the rendering of that particular component until after the data is loaded. Consider putting a loading gif (or something similar) in place of the form for the AJAX call.
I don't think the second way - using value and updating with onChange - is as bad as you say; it's generally how I write my forms. However, the real problem here is once again the delayed loading. By the time the initial value loads in, a user may already have filled in that input, only to see it overwritten with the received AJAX value. Not fun.
The best way in my view is simply to not use AJAX. Append your initial data to the webpage itself as a global variable. This may sound like an anti-pattern but you only ever read the global once, and it saves you cost of an AJAX request. In Redux there's a convenient way of doing this which I've documented here and here.
Just to add to David's answer.
Sometimes it does make sense to make an AJAX call to load defaults.
For ex. you could be calling some service for multi language support, creating an element in the data base with default values, etc.
I would go with the value-onChange approach and prevent the user from editing the value before it is loaded:
By disabling the input until the AJAX call returns. Just bind the disabled property to some prop that shows the default was loaded.
By not rendering the input until the AJAX call returns. When you get the default value from the server you modify the state so it triggers a re render.

Resources