React - Access the state of a parent from the children, without nested function - javascript

Hello,
I am coming to you today for the first time because I have not found a solution to my problem.
I have been using react for a few weeks, Don't be too cruel about the quality of my code ๐Ÿ˜.
Problem :
I am looking to access the state of a parent from their children.So I want to be able to access the setHeight function and the height variable for example from a child component.
Please note :
However, to keep some flexibility, I don't want to have any Components inside our.
I looked at redux to be able to do this, but the problem is that the data is global so creating multiple dropdowns would not be possible.
(Unless I didn't understand too much, redux is quite complex)
Diagram :
I have created a diagram to explain it a little better.,
I'd like the children of DropdownMenu to be able to access the state of the latter, Also, the different Dropdowns must have their own state independently.
So ideally I want to keep the same structure as find very flexible, and the possibility to create several dropdown.
Code :
I Share my four components :
export default function Navbar () {
return (
<nav className={styles.navbar}>
<ul className={styles.navbarNav}>
<NavItem icon={<NotificationsIcon />} />
<NavItem icon={<AccessTimeFilledIcon />} />
<NavItem icon={<FileOpenIcon />}>
<DropdownMenu>
<DropdownSubMenu menuName="Home">
<DropdownItem>My Profile</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="pages">Pages</DropdownItem>
<DropdownItem>IDK</DropdownItem>
<DropdownItem>Test</DropdownItem>
</DropdownSubMenu>
<DropdownSubMenu menuName="pages">
<DropdownItem>Pages</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="home">Home</DropdownItem>
</DropdownSubMenu>
</DropdownMenu>
<DropdownMenu>
<DropdownSubMenu menuName="config">
<DropdownItem>Foo</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="theme">Configuration</DropdownItem>
<DropdownItem>Bar</DropdownItem>
<DropdownItem>Baz</DropdownItem>
</DropdownSubMenu>
<DropdownSubMenu menuName="theme">
<DropdownItem>Hi StackOverflow</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="config">Theme</DropdownItem>
</DropdownSubMenu>
</DropdownMenu>
</NavItem>
</ul>
</nav>
);
};
type Props = {
children?: React.ReactNode | React.ReactNode[];
leftIcon?: React.ReactNode | JSX.Element | Array<React.ReactNode | JSX.Element>;
rightIcon?: React.ReactNode | JSX.Element | Array<React.ReactNode | JSX.Element>;
goToMenu?: string;
goBack?: boolean;
OnClick?: () => void;
};
export default function DropdownItem({ children, leftIcon, rightIcon, goToMenu, goBack, OnClick }: Props) {
const handleClick = OnClick === undefined ? () => { } : OnClick;
return (
<a className={styles.menuItem} onClick={() => {
goToMenu && setActiveMenu(goToMenu);
setDirection(goBack ? 'menu-right' : 'menu-left');
handleClick();
}}>
<span className={styles.iconButton}>{leftIcon}</span>
{children}
<span className={styles.iconRight}>{rightIcon}</span>
</a>
);
}
type Props = {
menuName: string;
children: React.ReactNode | React.ReactNode[];
}
enum Direction {
LEFT = 'menu-left',
RIGHT = 'menu-right'
}
export default function DropdownSubMenu (props: Props) {
const [direction, setDirection] = useState<Direction>(Direction.LEFT);
const calcHeight = (element: HTMLElement) => {
if (element) setMenuHeight(element.offsetHeight);
};
return (
<CSSTransition in={activeMenu === props.menuName} unmountOnExit timeout={500} classNames={direction} onEnter={calcHeight}>
<div className={styles.menu}>
{props.children}
</div>
</CSSTransition>
);
}
type Props = {
children: React.ReactNode | React.ReactNode[];
}
export default function DropdownMenu (props: Props) {
const [activeMenu, setActiveMenu] = useState<string>('home');
const [menuHeight, setMenuHeight] = useState<number | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const child = dropdownRef.current?.firstChild as HTMLElement;
const height = getHeight(child);
if (height)
setMenuHeight(height);
}, []);
return (
<div className={styles.dropdown} style={{ height: `calc(${menuHeight}px + 2rem)` }} ref={dropdownRef}>
{props.children}
</div>
);
}
Conclusion :
More concretely I don't know what to put instead :
In DropdownSubMenu to set the menu height (setMenuHeight), and gets the active menu (activeMenu).
In DropdownItem, set the active menu, (setActiveMenu) and set the direction of the CSS animation (setDirection).
Source :
My code is adapted from these sources, But I want to make this code more professional, flexible and polymorphic :
https://github.com/fireship-io/229-multi-level-dropdown
I've been tried :
I tried to look at Redux, but I understood that it was only state global.
So it doesn't allow to define a different context for each component.
I tried to look at React 18, without success.
I have searched the StackOverflow posts, I have searched the state retrieval from the parents.
The use of components inside a component solves in effect the problem but we lose all the flexibility.

There are multiple ways to access a parent state from its children.
Pass the state as props
The preferred way is to pass the state and/or the change function to the children.
Example :
const App = () => {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<button onClick={handleOpen}>Open modal</button>
<Modal onClose={handleClose} open={open} />
</div>
);
};
const Modal = ({ open, onClose }) => (
<div className={open ? "open" : "close"}>
<h1>Modal</h1>
<button onClick={onClose}>Close</button>
</div>
);
ReactDOM.render(<App />, document.querySelector("#app"));
Demo: https://jsfiddle.net/47s28ge5/1/
Use React Context
The first method becomes complicated when the children are deeply nested and you don't want to carry the state along the component tree.
You can then share a state across multiple children by using context.
const AppContext = React.createContext(undefined);
const App = () => {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<AppContext.Provider value={{ open, onClose: handleClose }}>
<div>
<button onClick={handleOpen}>Open modal</button>
<Modal />
</div>
</AppContext.Provider>
);
};
const Modal = () => {
const { open, onClose } = React.useContext(AppContext);
return (
<div className={open ? "open" : "close"}>
<h1>Modal</h1>
<button onClick={onClose}>Close</button>
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
Demo: https://jsfiddle.net/dho0tmc2/3/
Using a reducer
If your code gets even more complicated, you might consider using a store to share a global state across your components.
You can take a look at popular options such as:
Mobx: https://mobx.js.org/ (my personal favorite)
Redux: https://redux.js.org/
RxJS: https://rxjs.dev/

I can say welcome to react in this moment and i glad for you
OK, i could understand what is your problem.
but there isn't problem and this bug cause from your low experience.
As i understand you want to click on a dropdown and open it.
and here we have nested dropdown.
I think it's your answer:
You should declare a state on each dropdown and don't declare state in parent.

Related

How to type property based on other prop with Typescript and React?

How to make an intelligent prop type? I have Alert component which has some actions. Those actions can be clickable, there are some different components like Button or Link.
I would like to achieve something like this:
<Alert actions={[{ component: Link, props: { /* here only Link props available */ } }]} />
and
<Alert actions={[{ component: Button, props: { /* here only Button props available */ } }]} />
So props property should determine its type based on component property. Is this possible? I do not want to add any additional generics like
<Alert<ButtonProps> ... />
it should be "intelligent" and do it automatically
You can do this by generics, but it can get a little bit complicated: you need to explicit start which components are to be accepted by <Alert> via its prop type:
interface AlertAction<TFunctionalComponent extends FC<any>> {
component: TFunctionalComponent;
props: ComponentPropsWithoutRef<TFunctionalComponent>;
}
interface Props {
actions: Array<AlertAction<typeof Link | typeof Button>>;
}
export const Alert: FC<Props> = ({ actions }) => {
// Alert component body here
};
However I do see this as an anti-pattern: instead of splitting the component name and props into two separate keys, what if you simply let actions accept an array of ReactElement? i.e.:
interface Props {
actions: ReactElement[];
}
const Alert: FC<Props> = ({ actions }) => {
return <div>
{actions.map(action => <>{action}</>)}
</div>;
};
const MyApp: FC = () => {
return (
<>
{/* Will work */}
<Alert actions={[<Link {...linkProps} />]} />
<Alert actions={[<Button {...buttonProps} />]} />
</>
);
};
If you need to update the props or inject some custom child node into these elements, then you can take advantage of React.cloneChildren:
const Alert: FC<Props> = ({ actions }) => {
return (
<div>
{actions.map((action) => (
<>
{cloneElement(action, {
children: <>Custom child node for action elements</>,
})}
</>
))}
</div>
);
};

How to use react hooks to pass a state from child component to the parent component?

I have the parent component as below. Here I want to set the setLoading function inside the child component
loading.
const ReportRechargeDetail = React.memo(() => {
const [selectedDates, setSelectedDates] = useState([]);
const [loading, setLoading] = useState(false);
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Row className="header-wrapper">
<div>
Select date range to proceed : <Space />
<RangePicker
format="YYYY-MM-DD"
onChange={onChange}
disabled={loading}
onOpenChange={onOpenChange}
disabledDate={(currentDate) => currentDate.isAfter(moment())}
/>
</div>
</Row>
{ selectedDates ? (
<Row className="header-wrapper">
<div>
<PrimeButton
buttonName={"Download report"}
onClick={onClickHeaderButton}
/>
</div>
</Row>
) : (<div></div>)
}
<Tabs defaultActiveKey="1" onChange={callback} type="card">
<TabPane tab="IN APP" key="1">
<GenerateReport
columns={columns}
reportType={"recharge-info/in-app"}
reportName={"Recharge Details "}
breadcrumbName={"RECHARGED_DETAILED_REPORT"}
fileDownloadUrl={""}
selectedDateRange={selectedDates}
isOpen={isOpen}
isLoadingCallBack={setLoading}
/>
</TabPane>
<TabPane tab="BALANCE" key="2">
<GenerateReport
columns={columns}
reportType={"recharge-info/online"}
reportName={"Recharge Details "}
breadcrumbName={"RECHARGED_DETAILED_REPORT"}
fileDownloadUrl={"Recharge"}
selectedDateRange={selectedDates}
isOpen={isOpen}
/>
</TabPane>
</Tabs>
</>
);
});
export default ReportRechargeDetail;
child component as below. But I am getting an error , Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
const GenerateReport = React.memo(
({
columns,
reportType,
reportName,
breadcrumbName,
fileDownloadUrl,
downloadReport,
downloadReportName,
selectedDateRange,
isOpen,
isLoadingCallBack
}) => {
const [show, setShow] = useState(false);
//const [isOpen, setIsOpen] = useState(false);
const [pageNumber, setPageNumber] = useState(1);
const [pageSize, setPageSize] = useState(10);
const [selectedDates, setSelectedDates] = useState(selectedDateRange);
const [performedActionList, setPerformedActionList] = useState([]);
const [totalCount, SetTotalCount] = useState(0);
const [loading, setLoading] = useState(false);
const [requiredTotal, setRequiredTotal] = useState(true);
const dispatch = useDispatch();
const getRecurrentData = (dateStrings) => {
isLoadingCallBack(true);
setLoading(true);
setShow(true);
});
};
return (
<>
{show ? (
<Spin spinning={loading}>
<ViewBoxBody
loading={loading}
showPrimaryButton={false}
columns={columns}
filterSearchBar={false}
dataSource={performedActionList}
onPaginationClick={onPaginationClick}
currentPage={pageNumber}
totalDataCount={totalCount}
pageSize={pageSize}
showSizeChanger={true}
onShowSizeChange={(curent, size) => setPageSize(size)}
hideOnSinglePage={false}
showTotal={(total) => {
return `Total ${total} items`;
}}
/>
<ViewBoxFooter
onPaginationClick={onPaginationClick}
currentPage={pageNumber}
totalDataCount={totalCount}
pageSize={pageSize}
showSizeChanger={true}
onShowSizeChange={(curent, size) => setPageSize(size)}
hideOnSinglePage={false}
showTotal={(total) => {
return `Total ${total} items`;
}}
/>
</Spin>
) : (
<div></div>
)}
</>
);
}
);
Instead of passing setLoading to your child component, wrap the hook call inside a user-defined method in your parent component.
...
const toggleLoading = (value) => {
setLoading(value)
}
...
return ...
and use that method in the child component's props isLoadingCallBack={toggleLoading}. Depending on the complexity, it would make sense to wrap the parent state in a Context [1] so that you can access the values/setters anywhere in the child tree.
[1] https://reactjs.org/docs/context.html
This error has 3 causes as listed in the error description
You might have mismatching versions of React and the renderer (such as React DOM)
You might be breaking the Rules of Hooks
You might have more than one copy of React in the same app
let's take number 2 first to make sure we are following the rules of hooks
Your code looks fine, but let's take a small piece of working code and we are sure about it to try it in your environment https://codesandbox.io/s/little-monad-0jo71?file=/src/App.js try the example here and put it in a separate page/route if it works then the problem will be with your code and we will investigate more in it.
now number 1
You might have mismatching versions of React and the renderer (such as
React DOM)
Suggested Solution:
Double-check your react and react-dom versions
now number 3
You might have more than one copy of React in the same app
Suggested Solution:
Try the solutions in this issue https://github.com/facebook/react/issues/13991

why does my prop cease to exist after initially existing when I pass it through to a functional component?

This is the function where I am passing the onClick prop (setShowModal is setState() from the useState hook):
<MyFunctionalComponent
onClick={() => setShowModal(true)}
...other props here
/>
This is the functional component that receives the prop:
export const MyFunctionalComponent = ({ onClick }) => {
return (
<section>
...other code here
{onClick && (<Button>{ctaText}</Button>)}
</section>
);
};
But the Button component never appears, because the prop onClick is undefined. When I console.log the prop inside the functional component, it initially prints the function in the console, but then prints two more times as undefined. Could someone explain why that would be? I got it to work by spreading ...props instead. But the console.log remains the same? I don't understand why. This is my first question on Stack Overflow, so feel free to give me feedback on how to ask better questions :)
The reason why you are receiving an 'undefined' response is because as #Zrogua mentioned, onClick is an event listener function rather than a persistent value (like state you define).
import React from "react";
const YourButton = ({ onClick }) => {
console.log(onClick);
return <section>{onClick && <button>here</button>}</section>;
};
const ParentDiv = () => {
return (
<div>
<h1>Button Props</h1>
<h2>Start editing to see some magic happen!</h2>
<YourButton onClick={() => console.log("CLICK")} />
</div>
);
};
export default ParentDiv;
Result of console.log():
function onClick() // index.js:27:25
The reason why this is because props are read-only. From the React Docs:
Whether you declare a component as a function or a class, it must never modify its own props ... Such functions are called โ€œpureโ€ because they do not attempt to change their inputs, and always return the same result for the same inputs.
Therefore your button will only show if the onClick function is defined. For example, if you did not give onClick a function or value, the button will not appear:
import React, { useState } from "react";
const YourButton = ({ onClick }) => {
console.log(onClick);
return (
<section>
{onClick && <button>This button is shown if a button is defined.</button>}
</section>
);
};
const ParentDiv = () => {
return (
<div>
<h1>Button Props</h1>
<YourButton onClick={() => console.log("CLICK")} />
<YourButton /> {/* You won't see this button because the function is not defined. */}
</div>
);
};
export default ParentDiv;
The button appears because the prop has a value that is not undefined (your onClick function), and because it is read-only, you cannot access that function in your child component.
Instead, (1) define the modal state in the parent component and (2) pass the state through props to the button like so:
import React, { useState } from "react";
const YourButton = ({ onClick }) => {
console.log(onClick);
return (
<section>
{onClick && <button>This button is shown if a button is defined.</button>}
</section>
);
};
const AltButton = ({ modal }) => {
return (
<section>
{modal && (
<button>This button is shown the modal state is passed.</button>
)}
</section>
);
};
const ParentDiv = () => {
const [modal, setModal] = useState(false);
return (
<div>
<h1>Button Props</h1>
<YourButton onClick={() => console.log("CLICK")} />
<YourButton />{" "}
{/* You won't see this button because the function is not defined. */}
<section>
<button onClick={() => setModal(!modal)}>OPEN MODAL</button>
</section>
{modal && <p>this is dependent on state</p>}
<AltButton modal={modal} />
</div>
);
};
export default ParentDiv;
Working CodeSandbox: https://codesandbox.io/s/stack-66715327-passingfunctions-92pzr
Finally, if I am reading between the lines and understanding correctly that you are looking to hide a button when a modal is open, here is a little modal wrapper trick I use for buttons that open modals: https://codesandbox.io/s/stack-66715327-modalwrapper-wvl54
You can't pass onClick, onClick is just an event listener. You should pass the state
<MyFunctionalComponent onClick={() => setShowModal(!showModal)}
showModal={showModal}
...other props here />
/>
export const MyFunctionalComponent = ({ showModal }) => {
return (
<section>
...other code here
{showModal && (<Button>{ctaText}</Button>)}
</section>
);
};
I believe this should work. Let me know if this is what you were looking for.
I think that rather then passing callback you should pass variable which decide if component should show or not. Check this example.
export const MyFunctionalComponent = ({ isShow, onClick }) => {
return (
<section>
...other code here
{isShow && <div>something</div>}
</section>
);
};
export default function App() {
const [showModal, setShowModal] = useState(false);
return (
<MyFunctionalComponent
isShow={showModal}
onClick={() => setShowModal(true)}
/>
);
}
I also suppose that you can make mistake and have something other on mind .. like this:
<section>
...other code here
<button onClick={ onClick }>something</button>}
</section>

Hooks Callback- react

I'm currently building a react app which has a component slider and I need to pass the data back to the parent, the only fact is that the child is a little bit complex hook and I've been unable to find something similar that can help me to implement on my project, this is what I have:
The child
function valuetext(value) {
return `${value}`;
}
export default function RangeSlider() {
const classes = useStyles();
const [value, setValue] = React.useState([0, 100000]);
const handleChange = (event, newValue) => {
var val = setValue(newValue);
//I guess here is when I'm suposed to send the info to the parent
};
return (
<div className={classes.root}>
<Typography id="range-slider" gutterBottom>
Kilometers
</Typography>
<Slider
value={value}
max={500000}
min={0}
step={1000}
onChange={handleChange}
valueLabelDisplay="auto"
aria-labelledby="range-slider"
getAriaValueText={valuetext}
/>
<div id="seats-labes">
<span>0km</span>
<span>50.0000km</span>
</div>
</div>
);
}
The parent:
function WebFilter(props) {
return (
<div className="filter-web-section">
<Accordion className="filter-accordion">
<Card className="card-section">
<Card.Body>
<RangeSlider/>
</Card.Body>
</Card>
</Accordion>
</div>
)
}
export default WebFilter;
The grandfather:
class ResultModel extends Component {
render() {
return (
<div>
<h1>Texto de prueba + boton</h1> <button>+</button>
<div className="SiteHeader">
<Header/>
</div>
<div className="cars-result-content">
<div className="cars-result-content__filters">
<WebFilter
/>
</div>
<div className="car-result-content-list">
<div className="car-result-list__counter-cars">
<p>400 vehicles</p>
</div>
<div className="car-result-content-list__statBar">
<StatBar/>
</div>
<div className="cars-result-page-list__ListCars">
<ResultsView/>
</div>
</div>
</div>
</div>
)
}
}
I've been reading about declaring the hook constants at the very first component (grandfather) but I haven't been able to find a way to pass the data through the father. Thanks in advance for any hint or help.
The question is a bit short on specifics, but from what I can gather, you just need to pass down a function from component 1 through component 2 to component 3.
It's pretty straightforward actually.
In your grandpa component, create a function you want to pass:
class ResultModel extends Component {
const func1 = (data) => {console.log(data)}
render() {
...
Pass it down to father:
...
<WebFilter func1={func1} />
...
In the father component, get func1 and pass it down to child:
function WebFilter(props) {
const {func1} = props;
return (
<div className="filter-web-section">
<Accordion className="filter-accordion">
<Card className="card-section">
<Card.Body>
<RangeSlider func1={func1} />
</Card.Body>
</Card>
</Accordion>
</div>
)
}
Then in child call it like so:
export default function RangeSlider({func1}) {
const classes = useStyles();
const [value, setValue] = React.useState([0, 100000]);
const handleChange = (event, newValue) => {
var val = setValue(newValue);
//I guess here is when I'm suposed to send the info to the parent
func1("your data")
};
...
...
If you want to learn something read about react concept called lifting the state up.
Read about lifting state up in react documentation
Or just google it read one or two articles if still don't get it then post a comment I'll write full code.

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