BernardA
BernardA

Reputation: 1523

React function not passing down from functional to functional component

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

console output

What am I missing?

Upvotes: 1

Views: 1053

Answers (2)

Ikechukwu Eze
Ikechukwu Eze

Reputation: 3171

The event object in toggleProductsMenu and toggleServicesMenu is not defined.

Upvotes: 1

user7269511
user7269511

Reputation:

So before I point out to two potential solutions, it's important to note that you use a rendermethod 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:

Stateless Component

Class Component

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

Related Questions