MUI Popper Component with Props keeps closing component on re-render - javascript

I am curious how to architect a component leveraging MUI's Popover component when there are dynamic props getting passed to a controlled Slider component inside of the Popover component — as well as the anchor element also getting dynamically updated as the value changes getting passed-down from a higher order component.
What is happening is that when the controlled child is updated by the user, it dispatches the change higher up the chain, driving new values down, which then re-renders the component, setting the anchorEl back to null. Here's a quick video in action:
I'm sure there is something straightforward I could do to avoid this. Any help is appreciated!
Here is abbreviated code:
function Component({ dynamicProps }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const { dispatch } = useContext();
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
const handleChange = (_, newValue) => {
dispatch({
body: newValue
});
};
const open = Boolean(anchorEl);
const id = open ? "simple-popover" : undefined;
return (
<div>
<Button
onClick={handleClick}
label={dynamicProps.label}
></Button>
<Popover
id={id}
open={open}
anchorEl={anchorEl}
onClose={handleClose}
>
<Box sx={{ minWidth: "200px", mx: 2 }}>
<Slider
value={dynamicProps.value}
onChange={handleChange}
/>
</Box>
</Popover>
</div>
);
}
I have tried separating the Slider into another component, to avoid the re-render, and using my context's state to grab the values that I need, hover that point seems moot, since I still need to reference the anchorEl in the child, and since the trigger also is leveraging dynamic props, it will re-render and keep null-ing the anchorEl.

Ok team. Figured this one all-by-myself 🤗
Here's what you don't want to do: If you're going to use context — use it both for dispatching and grabbing state. Don't drill-down state from a parent component that will trigger a re-render. For both the button label and the controlled Slider, as long as you use the state insider the Component function through your context hook, you won't trigger a re-render, making your popper disappear from the re-render.
Do this 👇
export default function Assumption({ notDynamicProps }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const { dispatch, state } = useRentalCalculator();
Not this 👇
export default function Assumption({ dynamicProps, notDynamicProps }) {
const [anchorEl, setAnchorEl] = React.useState(null);
const { dispatch } = useRentalCalculator();

Related

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} />}

How to set a ref when using React.cloneElement on a Function component?

I'm writing a Tooltip component and came across the following problem -
Function components cannot be given refs
My code looks like this
Component that includes the Tooltip:
<Tooltip title="Title" description="Description">
<Selection value={copy.value} label={copy.label} />
</Tooltip>
Tooltip component:
import React, { useRef } from 'react'
const Tooltip = props => {
const myRef = useRef()
const child = React.cloneElement(props.children, {
onMouseEnter: () => { /* placeholder */ },
onMouseLeave: () => { /* placeholder */ },
ref: myRef,
})
const TooltipOverlay = () => {
// will be a div wrapped in react Portal
return null
}
return (
<>
{child}
<TooltipOverlay />
</>
)
}
export default Tooltip
My goal is to not have a visible wrapper in the DOM like div/span or similar.
I need the ref to be able to relatively position TooltipOverlay in the Tooltip component.
All solutions are welcome as long as long as goals above are fulfilled.
I saw this done in rsuite library, not sure how.
Had the same issue and couldn't find a way to do what you wanted.
As a workaround and I guess the usual way is to let the user of the Tooltip pass the ref down and then you can intercept it with forwardRef.
const Tooltip = forwardRef((props, myRef) => {
// now you can clone without a ref and have access to the ref.
})
The user of the tooltip needs to pass a ref however:
const childRef = useRef()
<Tooltip ref={childRef}>
<div ref={childRef} />
</Tooltip>

React Parent/Child State Change Design Question

I have a component with two children, one of them is a button that toggles a state (modalVisible) that decides whether the other child, a modal, is visible.
I'm having trouble sharing the on/off state across the parent and the modal child. I tried keeping the state in the parent and then passing it as a prop to the child, but it wasn't rerendering the child everytime the parent state changed.
<CommentsModal visible={modalVisible} />
Inside CommentsModal.js...
import Modal from 'react-native-modal';
...
const CommentsModal = ({visible}) => {
const [modalVisible, setModalVisible] = useState(visible);
...
return <Modal visible={modalVisible} />
}
I considered keeping the state entirely in the parent, without passing it into CommentsModal, like so:
function renderModal() {
if (modalVisible) {
return <CommentsModal visible={true} />
} else {
return <View />
}
}
But I realized that there has to be a state inside CommentsModal because I need an "X" button that toggles the modal off.
I'm not sure what the best way to do this is... I could do redux, but since there is a dynamic number of these modals; I don't want my store to be that complicated. The only way I can think of is to move all of the modal code into the parent component, then they can share states easily, but it seems dirty to me. Does anyone have a solution?
Your intuition to keep the state in the parent component is correct. To implement the x button all you need is to pass a onClose prop to the modal which would be a function that sets modalVisible to false. so you'll end up with something like this:
// parent component
const ParentComponent = () => {
const [modalVisible, setModalVisible] = useState(false);
const openModal = () => setModalVisible(true);
const closeModal = () => setModalVisible(false);
return (
<div>
<CommentsModal visible={modalVisible} onClose={closeModal} />
<button onClick={openModal}>open the modal</button>
<p>other children here...</p>
</div>
)
}
// CommentsModal
const CommentsModal = (props) => (
<Modal visible={props.visible}>
<button onClick={props.onClose}>X</button>
<p>more modal content here...</p>
</Modal>
)

How can I acess value from textfield in another module in reactJS

I want to have access to the value of a textField in another module in reactjs. The module that wants to have access does not import the whole textfield but only needs access to the value. What is the best way to access the value variable in another module in reactjs?
Here is my functional textField component:
export default function textField(props) {
const [value, setValue] = React.useState("");
const handleChange = (event) => {
setValue(value);
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
You can send onTextFieldChange function as props whenever textField's value changes you can pass a value to onTextFieldChange function and you can use it in the parent component.
There is an alternate way, Redux
You should try to use redux for the shared state between components which are either not related directly(i.e. sibling components or have a lengthy hierarchy). For small applications, redux is overkilled so should be avoided.
The most likely option that comes to mind here is the concept of lifting state, in which the nearest ancestor component has some means by which it also keeps track of the state, and the passes it into the sibling that needs to track it. You could make this an optional feature of your module by allowing a onChangeCallback prop that is called on each change; this prop could then be passed a setSharedState hook that would set the state on the ancestor:
const ParentComponent = () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
And you update your module to something like:
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
This is just a rough example. Other options for passing around state freely would be using Redux or the Context API, but the former might be overkill for this one case and the latter probably not a great fit for a specific, single-use datapoint.
there are may option but the proper option in pass as props
const modle1= () => {
const [textfieldVal, setTextfieldVal] = useState();
return (
<TextField onChangeCallback={setTextFieldVal} />
);
}
export default function textField(props) {
const [value, setValue] = React.useState("");
const {onChangeCallback} = props;
const handleChange = (event) => {
setValue(value);
if (typeof onChangeCallback === 'function') {
onChangeCallback(event); // or value, I'm not sure which you should be using here, this might be incorrect
}
};
return (
<div>
<TextField
id="outlined-multiline-static"
label="Frage"
multiline
onClick={handleClickOpen}
rows={4}
value={value}
placeholder="hello"
variant="outlined"
style={{
backgroundColor: "white",
}}
/>
</div>
);
}
or you can use create context and use context and provide the parent to provide and the child as a consumer but the best option pass as a prop
I would recommend you use the parent-child nature of react in order to handle this. Since I'm not sure the relationship between the other module and this module, I'll provide a rough skeleton.
In a parent component:
export default function ParentComponent(){
const [status, updateStatus] = useState("")
return(
<TextView updateParent={updateStatus}>
</TextView>
)
}
Then in your child component:
const handleChange = (event) => {
setValue(value);
props.updateParent(value);
};
If they are siblings, I would use a parent component and then pass the state down to the sibling. Otherwise, as appropriate, use this parent child relationship to pass and update state.
HTH

export Hooks in React for Nested Components?

I'm exporting hooks with nested components so that the parent can toggle state of a child. How can I make this toggle work with hooks instead of classic classes or old school functions?
Child Component
export let visible;
export let setVisible = () => {};
export const ToggleSwitch = () => {
const [visible, setVisibile] = useState(false);
return visible && (
<MyComponent />
)
}
Parent
import * as ToggleSwitch from "ToggleSwitch";
export const Parent: React.FC<props> = (props) => {
return (
<div>
<button onClick={() => ToggleSwitch.setVisible(true)} />
</div>
)
}
Error: Linter says [setVisible] is unused variable in the child... (but required in the parent)
You can move visible state to parent like this:
const Child = ({ visible }) => {
return visible && <h2>Child</h2>;
};
const Parent = () => {
const [visible, setVisible] = React.useState(false);
return (
<div>
<h1>Parent</h1>
<Child visible={visible} />
<button onClick={() => setVisible(visible => !visible)}>
Toggle
</button>
</div>
);
};
If you have many child-components you should make more complex logic in setVisible. Put object to useState where properties of that object will be all names(Ids) of child-components
as you know React is one-way data binding so if you wanna pass any props or state you have only one way to do that by passing it from parent to child component and if the logic becomes bigger you have to make it as a global state by using state management library or context API with react hooks use reducer and use effect.

Categories