Reputation: 1523
I am quite used to class components and now trying to get more used to hooks, so I am diving into functional components.
Running into a problem tough, as I've been unable to pass a function down from one functional component to its functional component child.
In the parent component:
import React, { useState, useEffect } from 'react';
import { Link } from 'gatsby';
import { MenuList, MenuItem } from '@material-ui/core';
import { withCookies, Cookies } from 'react-cookie';
import { ExpandMore } from '@material-ui/icons';
import PropTypes from 'prop-types';
import styles from '../styles/nav.module.scss';
import NavDrawer from './navDrawer';
const activeStyle = {
color: '#445565',
backgroundColor: '#de1b',
borderColor: '#ced4da',
};
const NavMobile = (props) => {
const [isOpenProductsMenu, setIsOpenProductsMenu] = useState(false);
const [isOpenServicesMenu, setIsOpenServicesMenu] = useState(false);
const [isAdmin, setIsAdmin] = useState(false);
const { cookies, categories } = props;
const main = process.env.ROOT_CATEGORIES.split(',');
useEffect(() => {
if (cookies.get('roles')) {
const roles = cookies.get('roles');
setIsAdmin(!!roles.includes('ROLE_ADMIN') || roles.includes('ROLE_SUPERADMIN'));
}
});
const toggleProductsMenu = (isOpen) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setIsOpenProductsMenu(isOpen);
};
const toggleServicesMenu = (isOpen) => {
if (event.type === 'keydown' && (event.key === 'Tab' || event.key === 'Shift')) {
return;
}
setIsOpenServicesMenu(isOpen);
};
return (
<MenuList className={styles.navMobile} style={{ padding: '0 10px 0 0' }}>
<MenuItem disableGutters>
<Link to="/" activeStyle={activeStyle}>Home</Link>
</MenuItem>
<MenuItem disableGutters>
<Link to="/search" activeStyle={activeStyle}>Search</Link>
</MenuItem>
<MenuItem
onMouseEnter={() => toggleProductsMenu(true)}
onMouseLeave={() => toggleProductsMenu(false)}
className={styles.overrideItem}
disableGutters
>
<NavDrawer
type={main[0]}
open={isOpenProductsMenu}
categories={categories}
toggleMenu={toggleProductsMenu}
/>
<Link to="/" onClick={(event) => event.preventDefault}>
<div className={styles.itemWrap}>
{main[0]}
<ExpandMore />
</div>
</Link>
</MenuItem>
<MenuItem
onMouseEnter={() => toggleServicesMenu(true)}
onMouseLeave={() => toggleServicesMenu(false)}
className={styles.overrideItem}
disableGutters
>
<NavDrawer
type={main[1]}
open={isOpenServicesMenu}
categories={categories}
toggleMenu={toggleServicesMenu}
/>
<Link to="/" onClick={(event) => event.preventDefault}>
<div className={styles.itemWrap}>
{main[1]}
<ExpandMore />
</div>
</Link>
</MenuItem>
{isAdmin ?
(
<MenuItem disableGutters>
<Link to="/admin" activeStyle={activeStyle}>Admin</Link>
</MenuItem>
) : null}
</MenuList>
);
};
NavMobile.propTypes = {
cookies: PropTypes.instanceOf(Cookies).isRequired,
categories: PropTypes.array.isRequired,
};
export default withCookies(NavMobile);
But if I try to access the props within the child component as below, the toggleMenu
function will not be in the output;
And, of course, will throw a type error
.
export default function NavDrawer(props) {
const {
type,
open,
categories,
toggleMenu,
} = props;
console.log('props drawer', props);
<Drawer
id={`parentDrawer-${type}`}
onMouseEnter={() => setIsOpenSubDrawer(true)}
onMouseLeave={() => setIsOpenSubDrawer(false)}
className={classes.drawer}
variant="temporary"
open={open}
close={toggleMenu(false)}
What am I missing?
Upvotes: 1
Views: 1053
Reputation: 3171
The event object in toggleProductsMenu and toggleServicesMenu is not defined.
Upvotes: 1
Reputation:
So before I point out to two potential solutions, it's important to note that you use a render
method within the parent function call, and that render
methods are only used in Class functions, not Stateless functions.
If you want to use a Stateless function, then you should use return
.
Check out the difference in the docs
And here are screenshots:
Now onto the solution, Stateless Functions
aren't supposed to have methods and adding them is generally considered bad practice. I understand you want to get familiar with non-Class components but there's nothing wrong with using a mixture of both and that's usually how it's supposed to be done. If you want to add methods, you should use a Class Component
and be doing it like this:
class NavMobile extends Component {
constructor(props) {
super(props);
this.state = {
// Empty for now
}
}
toggleMenu = () => {
// Your code
}
render() {
return (
// More code
<Navbar toggleMenu={this.toggleMenu} />
// More code
)
}
}
The reason that it is considered bad practice is because the function toggleMenu()
will be redefined every time the component is called.
If you really want to go ahead with this, then you should declare the function outside of the component function so you declare the function only once and use the same reference.
You should try something along these lines:
const toggleMenu = (isOpen) => { ... };
And call it in the component like this
const NavMobile = (props) => {
return (
// Your code
<NavBar toggleMenu={toggleMenu.bind(null, propsYouWantToPass} />
)
}
Finally, it seems like you're looking for an event object inside of the function e.g. (event.type === 'keydown')
but no such event is passed since in the child component NavBar
you call it with a false
argument.
Let me know if it helps.
PS: My comment at the very beginning about render
vs. return
was targeted at your code before you made the edit. I'll still leave it here for posterity.
Upvotes: 1