Material-ui dialog flickering when using redux for open state - javascript

I am trying to move the open state for material-ui dialog to redux to prevent it from closing when a rerender occurs, but i having trouble with the dialog when a rerender occurs. Although the state is saved in redux and the dialog does stay open whenever a rerender occurs the open state stays open but the dialog does show the open animation (fading in) which is kinda annoying.
Students.js (parent component of the modal)
const Students = ({
app: { studentsPage: { savedAddDialogOpen }},
setStudentsPageAddDialogOpen}) => {
// Create the local states
const [dialogOpen, setDialogOpen] = React.useState(savedAddDialogOpen);
const dialogOpenRef = React.useRef(savedAddDialogOpen);
// Change redux dialog open
const setReduxDialogState = () => {
setStudentsPageAddDialogOpen(dialogOpenRef.current, savedAddDialogOpen);
};
// Open add student dialog
const dialogClickOpen = () => {
setDialogOpen(true);
dialogOpenRef.current = true;
setTimeout(() => setReduxDialogState(), 300);
};
// Close add student dialog
const dialogClose = () => {
setDialogOpen(false);
dialogOpenRef.current = false;
setTimeout(() => setReduxDialogState(), 300);
};
return (
<Container>
{/* Add student modal */}
<AddStudentModal dialogOpen={dialogOpen} dialogClose={dialogClose} />
</Container>
)
}
// Set the state for this component to the global state
const mapStateToProps = (state) => ({
app: state.app,
});
AddStudentModal.js
const AddStudentModal = ({
dialogOpen, dialogClose
}) => {
return (
<Dialog
open={dialogOpen}
>
{/* Lots of stuff*/}
<DialogActions>
<Button onClick={dialogClose}>
Close dialog
</Button>
</DialogActions>
</Dialog>
)
};
I hope this should be sufficient. I tried checking if the open state is actually correct when a rerender occurs and it is correct every time but it looks like the dialog is closed at a rerender no matter what the open state is and only a few ms later actually notices that it should be opened.
Any help would be really appreciated
Edit 1: Found out it has nothing to do with the open state coming from redux, if i use open={true} it still flashes, so probably a problem with material-ui itself?
Edit 2: PrivateRoute.js
const PrivateRoute = ({
auth: { isAuthenticated, loadingAuth },
user: { loggedInUser },
component: Component,
roles,
path,
setLastPrivatePath,
...rest
}) => {
useEffect(() => {
if (path !== '/dashboard' && path !== '/profile') {
setLastPrivatePath(path);
}
// Prevent any useless errors with net line:
// eslint-disable-next-line
}, [path]);
// If we are loading the user show the preloader
if (loadingAuth) {
return <Preloader />;
}
// Return the component (depending on authentication)
return (
<Route
{...rest}
render={props =>
!isAuthenticated ? (
<Redirect to="/login" />
) : (loggedInUser && roles.some(r => loggedInUser.roles.includes(r))) ||
roles.includes('any') ? (
<Component {...props} />
) : (
<NotAuthorized />
)
}
/>
);
};
// Set the state for this component to the global state
const mapStateToProps = state => ({
auth: state.auth,
user: state.user
});

I found the problem thanks to #RyanCogswell!
For anyone having the same problem here is the cause for me and the fix:
I was passing components into the Route component like this:
<PrivateRoute
exact
path={'/dashboard/students'}
component={(props) => (
<Students {...props} selectedIndex={selectedIndexSecondary} />
)}
roles={['admin']}
/>
By doing it this way i could pass props through my privateRoute function but this would also happen if you send the component this way in a normal Route component
Solution for me is just moving selectedIndexSecondary to redux and sending the component the normal way it prevented the re-mounting.
So just doing it like this will prevent this from happening.
<PrivateRoute
exact
path={'/dashboard/students'}
component={Students}
roles={['admin']}
/>
Also this will solve the localstates in your components from resseting to the default value. So for me it fixed two problems!

Related

React setting the state of all rendered items with .map in parent component

I have a parent component with a handler function:
const folderRef = useRef();
const handleCollapseAllFolders = () => {
folderRef.current.handleCloseAllFolders();
};
In the parent, I'm rendering multiple items (folders):
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
name={folder.name}
content={folder.content}
id={folder.id}
ref={folderRef}
/>
))}
In the child component I'm using the useImperativeHandle hook to be able to access the child function in the parent:
const [isFolderOpen, setIsFolderOpen] = useState(false);
// Collapse all
useImperativeHandle(ref, () => ({
handleCloseAllFolders: () => setIsFolderOpen(false),
}));
The problem is, when clicking the button in the parent, it only collapses the last opened folder and not all of them.
Clicking this:
<IconButton
onClick={handleCollapseAllFolders}
>
<UnfoldLessIcon />
</IconButton>
Only collapses the last opened folder.
When clicking the button, I want to set the state of ALL opened folders to false not just the last opened one.
Any way to solve this problem?
You could create a "multi-ref" - ref object that stores an array of every rendered Folder component. Then, just iterate over every element and call the closing function.
export default function App() {
const ref = useRef([]);
const content = data.map(({ id }, idx) => (
<Folder key={id} ref={(el) => (ref.current[idx] = el)} />
));
return (
<div className="App">
<button
onClick={() => {
ref.current.forEach((el) => el.handleClose());
}}
>
Close all
</button>
{content}
</div>
);
}
Codesandbox: https://codesandbox.io/s/magical-cray-9ylred?file=/src/App.js
For each map you generate new object, they do not seem to share state. Try using context
You are only updating the state in one child component. You need to lift up the state.
Additionally, using the useImperativeHandle hook is a bit unnecessary here. Instead, you can simply pass a handler function to the child component.
In the parent:
const [isAllOpen, setAllOpen] = useState(false);
return (
// ...
{folders &&
folders.map(folder => (
<CollapsableFolderListItem
key={folder.id}
isOpen={isAllOpen}
toggleAll={setAllOpen(!isAllOpen)}
// ...
/>
))}
)
In the child component:
const Child = ({ isOpen, toggleAll }) => {
const [isFolderOpen, setIsFolderOpen] = useState(false);
useEffect(() => {
setIsFolderOpen(isOpen);
}, [isOpen]);
return (
// ...
<IconButton
onClick={toggleAll}
>
<UnfoldLessIcon />
</IconButton>
)
}

React Component not re-rendering when prop changes

I have three components - layout, navigation, and room dropdown. Layout gets a property 'lobbyCode' from a database and sets it to a state, then passes it as a prop to Navigation. Navigation then passes this prop as a prop to RoomDropdown, however the room dropdown component does not re-render when the lobbyCode state is changed in layout. Navigation seems to handle the state change fine and re-renders correctly, so I'm not sure what's happening.
Layout:
const { user, isAuthenticated, isLoading } = useAuth0();
const [lobbyCode, setLobbyCode] = useState("");
return (
<div>
<Navigation GetUser={GetUser} GetPlayers={GetPlayers} lobbyCode={lobbyCode} user={user}
isAuthenticated={isAuthenticated} isLoading={isLoading} />
</div>
)
useEffect(() => {
if (isAuthenticated) {
GetUser(user.email).then(result => {
if (result.length > 0) {
setLobbyCode(result[0].lobby);
}
});
}
}, [user]);
Navigation:
<RoomDropdown lobbyCode={props.lobbyCode} GetUser={props.GetUser} GetPlayers={props.GetPlayers} user={props.user} />
RoomDropdown
useEffect(() => {
console.log(props.lobbyCode);
}, [props.lobbyCode]);
The result is that the room dropdown will just print undefined and never update, even once the lobby code is loaded.

Way to render a new component onClick in react js

Am trying to render a new component onclick a button in react js. Am using functional components and I can't handle it. Eg: am in the UserManagement component and on a button click I need to render another component named employee management.
You can conditionally render your component.
Example :
EmployeeManagement.js
const EmployeeManagement = () => {
....
return (
<div>
EmployeeManagement
</div>
);
}
UserManagement.js
const UserManagement = () => {
const [hasRender, setRender] = useState(false);
const onShow = React.useCallback(() => setRender(true), []);
return (
<>
<button onClick={onShow}>Show Employee Management</button>
{hasRender && <EmployeeManagement />}
</>
)
}
One way to do this would be to add a local state in UserManagement,
that holds a boolean value indication whether the component should be hidden or shown.
Then you will have something like:
function UserManagement() {
const [compIsShown, setCompIsShown] = useState(false);
return (
// Whatever else you're rendering.
<button onClick={() => setCompIsShown(true)}>...</button>
{compIsShown && <OtherComp />}
)
}
What will happen is that compIsShown will initialize as false,
so this condition compIsShown && <OtherComp /> will prevent it from rendering.
Then, when you click the button, the state will set, causing a re-render, except now the condition will be true, so <OtherComp> will be rendered.
There are other ways to go about this.
Depends mostly on the use-case.
use a visible state & toggle it in onClick:
const [visible, setVisible] = useState(false)
onClick = () => {setVisible(true)}
then render it like this:
{visible && <EmployeeManagement onClick={onClick} />}

Some React components not re-rendering after setState change

I have a form that has a two buttons on top, Sign Up / Log In, and one button on the bottom, Get Started. I am struggling to get the components to update with eachother. From the default Sign Up component, I can click on the Get Started button and the form disappears and shows the product components as it should. My issue is I cannot get the Log In component to display instead of the Sign Up.
The below is the code I am using - you will see where I have commented out two of my if statements where my intentions were trying to just have the app load whatever state its in. The last return with the ? statement seems to be tripping me up when I attempt to add the third component. I am able to correctly set the state to "loginShow", although nothing changes it just records in my console.
How can I correctly set state so it just toggles back and forth between Sign Up and Log In untill I press the Get Started button at the bottom?
clickMe = () => {
const {requestedPostsThatWeGotFromGecko} = this.state;
this.setState({ requestedPostsThatWeGotFromGecko: !requestedPostsThatWeGotFromGecko })
}
mainLogIn = () => {
// const {loginStatus} = this.state;
this.setState({ loginStatus: 'loginShow' })
}
render() {
const { requestedPostsThatWeGotFromGecko } = this.state;
const { loginStatus } = this.state;
console.log(loginStatus);
// if (loginStatus === "loginHide") return <SignUp login={() => this.clickMe()} />
if (loginStatus === "loginShow") return <Login />
// if (requestedPostsThatWeGotFromGecko.props === true) return <Cards />
return (
<div className="gecko">
{requestedPostsThatWeGotFromGecko ? (
<Cards />
): (
<SignUp login={() => this.clickMe()} />
)
}
</div>
);
}

Component props not updating in shallow render

I have a payment component and when I click a button, it should update the component state isOpen to true. This works fine in practice but when trying test it via enzyme, it wont update the prop.
The component looks like this:
class CashPayment extends Component {
state = {
isOpen: false
}
toggleModal = () => {
this.setState({ isOpen: true })
}
render() {
return (
<Mutation>
{() => (
<Fragment>
<Button
id="cash-payment-button"
onClick={this.toggleModal}
/>
<Modal
id="confirm-payment-modal"
isOpen={isOpen}
>
...
</Modal>
</Fragment>
)}
</Mutation>
)
}
}
So clicking #cash-payment-button should toggle the state isOpen which should open the modal.
In my test, I want to check the prop of my modal isOpen is set to true. But for some reason, the prop doesn't update in the test. However if I console log in my toggleIsOpen function, I can see the function gets called and the state updates.
My test is as so:
describe("Click Pay button", () => {
it("Should open confirm modal", () => {
Component = shallowWithIntl(
<CashPayment bookingData={bookingData} refetchBooking={refetchBooking} />
)
.dive()
.dive()
const button = Component.find("#cash-payment-button")
.props()
.onClick()
expect(Component.find("#confirm-payment-modal").prop("isOpen")).toEqual(true)
})
})
and the results are:
CashPayment › Click Pay button › Should open confirm modal
expect(received).toEqual(expected)
Expected value to equal:
true
Received:
false
Why does the modal component props not update?

Categories