Reset state value in functional component in React native - reactjs

I have created cart functionality and I have managed this using useState hook. though I can change quantity of items but if I close the Modal, it can't reset. (I want quantity 0 on closing a Modal).
let [addonData, setAddonData] = useState([]);
Here if i open modal again after closing I get previous values, but I want 0 instead of that. Please check attached image for your reference.
I have tried but I got this error: too many re-renders. react limits the number of renders to prevent an infinite loop.
So, If anyone have idea then please let me know.
Thanks in advance :)

For doing that, you'll probably need to fire a reset function on modal dismiss, but what that means exactly?!
Parent Component
Imagine that we have this modal which receives a state from the parent component.
const MyParentComponent = () => {
const [opened, setOpened] = useState(false);
const [state, setState] = useState([])
const onOpen = () => {
setOpened(true)
}
const onDismiss = () => {
setOpened(false)
setState([])
}
return (
<>
{myComponent()}
<Modal isOpened={opened} state={state} setState={setState} onDismiss={onDismiss} />
</>
)
}
What is happening here is that we pass a state from the Parent to the Modal Component, but every time we close the modal we reset the state we were passing to it.
This solve your problem, but check this section of the beta.reactjs docs

Related

Why would useRef object be null even after attaching to a div element?

Hey yall Im having a weird and annoying issue while trying to useRef on a div element. I have this working exactly as it is on another page but this doesnt seem to be doing what I want it to on this page.
Im trying to implement and endless scroll. The goal is to attach the ref (refetchTrigger) to a certain div on the page and have that trigger a fetch for the next page of data when scrolled into view. It seems to render the div correctly but refetchTrigger is not updated to be the div, it just remains null. Seems like a rerender needs to happen here but obviously changes to refs dont trigger a rerender. Ive been battling with this all morning and would greatly appreciate any suggestions. In the code snippet below, all console.log(refetchTrigger.current) are printing out null.
Its also worth noting that the refetch call is using useSWR hook to fetch data. when removing this the attaching of ref to div seemed to work correctly. also, when adding a button to fetch its fetching as expected. its just trying when trying to automatically trigger the fetch that Im seeing the issue.
Thanks for the help!
export const TrackGrid = () => {
const [list, setList] = useState<Track[]>([]);
const [page, setPage] = useState<number>(1);
const refetchTrigger = useRef<HTMLDivElement | null>(null);
const inViewport = useIntersection(refetchTrigger, "0px");
const { tracks, error, isValidating } = useGetTracks(false, page, 20);
useEffect(() => {
if (inViewport) {
setPage(page + 1);
}
console.log("in viewport");
}, [inViewport]);
useEffect(() => {
if (tracks) setList([...list, ...tracks]);
}, [tracks]);
const renderDiv = () => {
console.log(refetchTrigger.current);
const d = <div ref={refetchTrigger}>exists</div>;
console.log(refetchTrigger.current);
return d;
};
return (
<>
<div className="grid place-items-center grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-4">
{!!list.length && renderDiv()}
{list.map((track: Track, i: number) => {
console.log(refetchTrigger.current);
return (
<div ref={refetchTrigger} key={i}>
<TrackGridItem track={track} />
</div>
);
})}
</div>
</>
);
};
Here is the code thats interacting with the ref
```export const useIntersection = (element: any, rootMargin: string) => {
const [isVisible, setState] = useState<boolean>(false);
useEffect(() => {
const observer = new IntersectionObserver(
([entry]) => {
setState(entry.isIntersecting);
},
{ rootMargin }
);
element.current && observer.observe(element.current);
return () => element.current && observer.unobserve(element.current);
}, []);
return isVisible;
};```
The ref only gets populated after the render is complete, and the element has been created by react on the actual dom. Logging the ref out during the render will not work (unless there has been a previous render).
The fix is to put your code that needs to interact with the ref into a useEffect. That way by the time your code runs, the render is complete and the element is on the page.
useEffect(() => {
console.log(refetchTrigger.current)
}, []);
Essentially the ref will not be populated until after the whole render pass is finished. This is not obvious to many React programmers (and it usually doesn't matter) but the DOM is not actually committed until later on. When you call renderDiv() and pass the ref, on the first mount the element is not even rendered in the DOM at the stage that code executes. React effectively executes and renders the virtual DOM tree into the real DOM shortly after the render pass.
If you have code that is reliant on the DOM node existing, because you need to read something for whatever reason you need to run it an effect or (this is something you have to be careful with), run it in a ref callback.
The fix for me was updating the useEffect to remove the dependency array. According to the new react docs, the recommended read or write example is in a useEffect without a dependency array (runs every time).
In my above example the ref is being used in the useIntersection hook. I removed the dependency array and it worked as expected

Why React is rendering parent element, even if changed state isn't used in jsx? (Using React Hooks)

Im doing a React small training app using Hooks. Here's the example:
There is a MainPage.js and it has 3 similar child components Card.js. I have global state in MainPage and each Card has its own local state. Every Card has prop "id" from MainPage and clickButton func.
When I click button in any Card there are 2 operations:
Local variable 'clicked' becomes true.
The function from parent component is invoked and sets value to global state variable 'firstCard'.
Each file contains console.log() for testing. And when I click the button it shows actual global variable "firstCard", and 3x times false(default value of variable "clicked" in Card).
It means that component MainPage is rendered after clicking button ? And every Card is rendered too with default value of "clicked".
Why MainPage componenet is rendered, after all we dont use variable "firsCard", except console.log()?
How to make that after clicking any button, there will be changes in exactly component local state, and in the same time make global state variable "firstCard" changed too, but without render parent component(we dont use in jsx variable "firstCard")
Thanks for your help !
import Card from "../Card/Card";
const Main = () => {
const [cards, setCards] = useState([]);
const [firstCard, setFirstCard] = useState(null);
useEffect(() => {
setCards([1, 2, 3]);
}, []);
const onClickHandler = (id) => {
setFirstCard(id);
};
console.log(firstCard); // Showing corrrect result
return (
<div>
{cards.map((card, i) => {
return (
<Card
key={Date.now() + i}
id={card}
clickButton={(id) => onClickHandler(id)}
></Card>
);
})}
</div>
);
};
import React, { useState } from "react";
const Card = ({ id, clickButton }) => {
const [clicked, setClicked] = useState(false);
const onClickHandler = () => {
setClicked(true);
clickButton(id);
};
console.log(clicked); // 3x false
return (
<div>
<h1>Card number {id}</h1>
<button onClick={() => onClickHandler()}> Set ID</button>
</div>
);
};
export default Card;
You have wrong idea how react works.
When you change something in state that component will re render, regardless if you use that state variable in render or not.
Moreover, react will also re render all children of this component recursively.
Now you can prevent the children from re rendering (not the actual component where state update happened though) in some cases, for that you can look into React.memo.
That said prior to React hooks there was a method shouldComponentUpdate which you could have used to skip render depending on change in state or props.

React access state after render with functional components

I'm a bit of a newbie with React functional components, I have a child and parent components with some state that gets updated with useEffect, which state apparently resets back to its initial values after render.
Parent has a list of users it passes to its child:
Parent:
const Parent = () => {
const [users, setUsers] = useState([])
const getUsers = () => {
setUsers(["pedro", "juan"])
}
useEffect(() => {
getUsers()
}, []);
return <div>
<Child users={users} />
}
Child:
const Child = () => {
const [users, setUsers] = useState([])
useEffect(() => {
setUsers(props.users)
}, [[...props.users]]);
}
If I for any reason try to access state (users) from either my child or parent components I get my initial value, which is an empty array, not my updated value from getUsers(), generally with a Parent Class component I'd have no trouble accessing that info, but it seems like functional components behave diffently? or is it caused by the useEffect? generally I'd use a class component for the parent but some libraries I use rely on Hooks, so I'm kind of forced to use functional components.
There are a couple of mistakes the way you are trying to access data and passing that data.
You should adapt to the concept of lifting up state, which means that if you have users being passed to your Child component, make sure that all the logic regarding adding or removing or updating the users stays inside the Parent function and the Child component is responsible only for displaying the list of users.
Here is a code sandbox inspired by the code you have shared above. I hope this answers your question, do let me know if otherwise.
Also sharing the code below.
import React, { useState } from "react";
export default function Parent() {
const [users, setUsers] = useState([]);
let [userNumber, setUserNumber] = useState(1); // only for distinctive users,
//can be ignored for regular implementation
const setRandomUsers = () => {
let newUser = {};
newUser.name = `user ${userNumber}`;
setUsers([...users, newUser]);
setUserNumber(++userNumber);
};
return (
<div className="App">
<button onClick={setRandomUsers}>Add New User</button>
<Child users={users} />
</div>
);
}
const Child = props => {
return (
props.users &&
props.users.map((user, index) => <div key={index}>{user.name}</div>)
);
};
it doesnt make sense to me that at Child you do const [users, setUsers] = useState([]). why dont you pass down users and setUsers through props? your child's setUser will update only its local users' state value, not parent's. overall, duplicating parent state all around its children is not good, you better consume it and updating it through props.
also, once you do [[...props.users]], you are creating a new array reference every update, so your function at useEffect will run on every update no matter what. useEffect doesnt do deep compare for arrays/objects. you better do [props.users].

How to Unmount React Functional Component?

I've built several modals as React functional components. They were shown/hidden via an isModalOpen boolean property in the modal's associated Context. This has worked great.
Now, for various reasons, a colleague needs me to refactor this code and instead control the visibility of the modal at one level higher. Here's some sample code:
import React, { useState } from 'react';
import Button from 'react-bootstrap/Button';
import { UsersProvider } from '../../../contexts/UsersContext';
import AddUsers from './AddUsers';
const AddUsersLauncher = () => {
const [showModal, setShowModal] = useState(false);
return (
<div>
<UsersProvider>
<Button onClick={() => setShowModal(true)}>Add Users</Button>
{showModal && <AddUsers />}
</UsersProvider>
</div>
);
};
export default AddUsersLauncher;
This all works great initially. A button is rendered and when that button is pressed then the modal is shown.
The problem lies with how to hide it. Before I was just setting isModalOpen to false in the reducer.
When I had a quick conversation with my colleague earlier today, he said that the code above would work and I wouldn't have to pass anything into AddUsers. I'm thinking though that I need to pass the setShowModal function into the component as it could then be called to hide the modal.
But I'm open to the possibility that I'm not seeing a much simpler way to do this. Might there be?
To call something on unmount you can use useEffect. Whatever you return in the useEffect, that will be called on unmount. For example, in your case
const AddUsersLauncher = () => {
const [showModal, setShowModal] = useState(false);
useEffect(() => {
return () => {
// Your code you want to run on unmount.
};
}, []);
return (
<div>
<UsersProvider>
<Button onClick={() => setShowModal(true)}>Add Users</Button>
{showModal && <AddUsers />}
</UsersProvider>
</div>
);
};
Second argument of the useEffect accepts an array, which diff the value of elements to check whether to call useEffect again. Here, I passed empty array [], so, it will call useEffect only once.
If you have passed something else, lets say, showModal in the array, then whenever showModal value will change, useEffect will call, and will call the returned function if specified.
If you want to leave showModal as state variable in AddUsersLauncher and change it from within AddUsers, then yes, you have to pass the reference of setShowModal to AddUsers. State management in React can become messy in two-way data flows, so I would advise you to have a look at Redux for storing and changing state shared by multiple components

React Ant Design Modal Method update on state change

I'm currently migrating to antd, and have a modal appear on a certain route (ie /userid/info). I'm able to achieve this if I use the antd Modal react component, but I'd like to be able to use the modal methods provided such as Modal.confirm,Modal.info and Modal.error as they offer nicer ui straight out of the box.
I'm running to multiple issues such as having the modal rendered multiple times (both initially and after pressing delete in the delete user case), and unable to make it change due to state (ie display loading bar until data arrives). This is what i've tried but it constantly renders new modals, ive tried something else but that never changed out of displaying <Loader /> even though isFetching was false. I'm not sure what else to try.
const UserInfoFC: React.FC<Props> = (props) => {
const user = props.user.id;
const [isFetching, setIsFetching] = React.useState<boolean>(true);
const [userInfo, setUserInfo] = React.useState<string>('');
const modal = Modal.info({
content: <Loader />,
title: 'User Info',
});
const displayModal = () => {
const renderInfo = (
<React.Fragment>
<p>display user.info</p>
</React.Fragment>
);
const fetchInfo = async () => {
try {
user = // some api calls
setUserInfo(user.info);
modal.update({ content: renderInfo })
} catch (error) {
// todo
}
setIsFetching(false);
};
fetchInfo();
};
displayModal();
return(<div />);
};
reference: https://ant.design/components/modal/#components-modal-demo-confirm
edit: here is a replication of one of the issues I face: https://codesandbox.io/embed/antd-reproduction-template-1jsy8
As mentioned in my comment, you can use a useEffect hook with an empty dependency array to run a function once when the component mounts. You can initiate an async call, wait for it to resolve and store the data in your state, and launch a modal with a second hook once the data arrives.
I made a sandbox here
Instead of going to /:id/info and routing to a component which would have returned an empty div but displayed a modal, I created a displayInfo component that displays a button and that controls the modal. I got rid of attempting to use routes for this.
What I have now is similar to the docs

Resources