Why is useState failing to read initialization value? - reactjs

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.

Related

In React, is it possible to memoize a list of components and their internal state?

[This is the Codesandbox with all of the code that exemplifies the issue I'm having. Sorry, SO doesn't allow for proper React code with TSX, etc.]
I have some tabs, naturally, whenever I click on each of them, my state changes (namely selectedTab) and the body that's being rendered. Each tab item has a body that goes with it, the first tab, in my case, has a component that, if you click, a count is increased. Well, whenever the tab changes, that count gets reset. In fact, it's a full-rerender all around.
The problem with doing a full re-render is that - not only is it inefficient, given how the user's gonna use it, but it also adds fuel to the fire: what if the user inputs something on a tab, then switches to another tab to do something else and then back to the original one? He's just lost what he typed in.
Naturally, this can be solved if we render all of the tabs' content upfront and we just play with display: none, and it'd be a good solution, but I'm also trying to learn.
I understand how React re-renders things and why this happens, but is there a way to fix it?
In my case, if you look at useTabs, the content variable simply looks at the corresponding tab's component key and takes outputs that. It's there that, I think, I need to memoize things.
Uhm, I guess you can't prevent calling your custom hook, this will lead to invariant violation error.
These hooks in the component will be executed on each render -> thus 'resets' your tabs.
You should rely on useEffect dependencies argument to run conditional code to see if values needs to be changed.
as long as your in the same app you could use contextApi,react context docs otherwise you can use localstorage, localstorage tutorial
or just create this state in the parent component and pass it down as props?

React parent to child props passing without rerender

I have a button on my parent component and a third-party form in a child component. When the user clicks the button, the save function in the child component should run. This is my plan.
Method 1 I tried:
1.Created a variable on parent called save.
2. When button is clicked, save becomes true
3. Save is passed down to the child as props
4. Child contains a useEffect() which listens to changes in props.save
5. If props.save is true, save function of child component runs
Method 2 I tried:
Instead of passing props, I created a react-redux store.
The store contains save variable which is false by default
When button is clicked, save in redux becomes true
I use useSelector() hook to listen to the save variable change, inside the child component
UseEffect is used to run the save() function when the value change is detected
What happens with both methods is that I am losing the data in my child component because the variable change in the parent causes a page refresh. Therefore I want to pass the data down to the child, without causing rerenders. What are the options I have?
Thanks in advance
Thanks to #Shyam, I could finally solve this issue!
My assumption that useState and props cause render was correct. And as #Shyam suggested, there is no direct way to avoid it.
I am using react-editor-js in my project and that's what caused the issue
<EditorJs
instanceRef={()=>{//code to save the reference as state variable}}
enableReInitialize
data={{}}
tools={EDITOR_JS_TOOLS}
/>
The reason for state loss was that my instanceRef was being reassigned every time the component renders.
This reassignment can be prevented by wrapping the method to save the reference as a state variable with useCallback()

React Routing causes inputs to lose focus

I have an app with a component that has inputs in it. When the user changes the inputs, I want to update the query string in the URL to reflect the new state. I achieve this by detecting a difference between the current location.search values and the query string that my state would generate. If there's a difference, I return a <Redirect> component with the query args reflecting the new state.
The problem is that after every such redirect, the component re-renders and my inputs are replaced by new inputs, meaning that the input I just changed no longer exists, so it doesn't have focus. If it's a text input, then it loses focus after every keystroke.
How I dealt with it was to remember the ID of the element that had focus when the state changed and then set focus to the new element based on document.getElementById once the component is done re-rendering.
I've reduced the issue down to a minimal app that is at https://codesandbox.io/s/github/TrevorMills/react-routing-conundrum. The problem manifests on both functional components and React.Component classes.
My mechanism seems hacky to me. My question to the React experts out there is, have I missed something? Is there a better way to get the state into the location query parms than <Redirect>? I tried adding key to my inputs but it didn't make a difference. If my mechanism seems legit, is there a way to apply it to the app on whole in a higher order component?
Thanks in advance.
Since you were using redirect, on every change of input value, the component rendering toggles between the returned value and the redirect causing all components to remount instead of re-render. This causes the input field to lose focus.
Avoid using <Redirect /> and use history.replace after updating the state.
Instead of doing history.replace in onChangeText function, you can write a useEffect, like this.
useEffect(() => {
props.history.replace(props.location.pathname + "?" + qs.stringify(state));
}, [state]);
Updated working example is here:
https://codesandbox.io/s/cocky-swirles-kcqwo?file=/src/App.js
I've edited your functional component version of the code, however the changes I made should be applicable to both.
The main thing I did is I removed the state duplication. Instead of duplicating the state from the route to the component, I just made the component use the query params directly as state. And then I made the updateState function update the query params using props.history.replace which is how React Router allows imperative navigation.
Hope this helps!
https://codesandbox.io/s/great-williamson-0pxyb

React state updating unexpected

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

Multiple Slate.js editors / prevent redux from re-rendering parent component

I am trying to add multiple Slate.js text editor fields into one page. So far my main component contains a button which when clicked appends a slate value to an array slateValues in my redux store. In the render function I then map over that array and for each entry return a SlateEditor component which essentially renders the standard Slate Editor component with some custom formatting/functionality.
My problem is that Slate uses an onChange function to process changes to the value. Handling those changes in the local state works fine, but how can I pass that into the redux store? Updating the redux store directly in the onChange causes the parent component to re-render which then ends up in an endless loop (I assume this then triggers the onChange again, which triggers a re-render etc.).
I initially passed down the values as props into the SlateEditor component, then tried to directly read the value in the child component (SlateEditor) from the redux store.
My final aim is to store the slateValues as a "block" in a database. Any ideas on how to fix this? Thanks
I had similar issue.
What I did was I did not map over the array of slate values.
Instead I had another array of (my case) editorsNamespaces=[....] also I pass to the Slate editor wrapper component a selector (ie: selectSlateValueByEditorByNamespace) and a onChangeHandler(newValue, nameSpace).
By doing that my parent component will not re-render all editor because editorsNamespaces array will never change, and only particular editor will re-render when value has changed.

Resources