1

I'm new to reactjs and this may be a basic knowledge, but I cannot find any related information on the internet.

I have the following code that pops up a Modal window when you click some button (clicking the button results in history.push(url) action, which pops up the modal window ):



const ProjectBoard = () => {
  const match = useRouteMatch();
  const history = useHistory();
  const [filters, mergeFilters] = useMergeState(defaultFilters);

  const [IssueCreateModalOpen, setIssueCreateModalOpen] = useState(false);

  const [{ data, error, setLocalData }, fetchProject] = useApi.get('/project');

  if (!data) return <PageLoader />;
  if (error) return <PageError />;

  const { project } = data;

  const updateLocalProjectIssues = (issueId, updatedFields) => {
    setLocalData(currentData => ({
      project: {
        ...currentData.project,
        issues: updateArrayItemById(currentData.project.issues, issueId, updatedFields),
      },
    }));
  };
  
  return (
      <Fragment>
      <Header/>
      <Lists
        project={project}
        filters={filters}
        updateLocalProjectIssues={updateLocalProjectIssues}
      />

      <br/>

      <Route
        path={`${match.path}/issues/:issueId`}
        render={routeProps => (
          <Modal
            isOpen  // confusion 1: this variable is not defined anywhere!! 
            testid="modal:issue-details"
            width={1040}
            withCloseIcon={false}
            onClose={()=>history.push(match.url)}
            renderContent={modal => (
              <IssueDetails
                issueId={routeProps.match.params.issueId}
                trigger={routeProps.location.state.trigger}
                projectUsers={project.users}
                fetchProject={fetchProject}
                updateLocalProjectIssues={updateLocalProjectIssues}
                modalClose={modal.close}
              />
            )}
          />
        )}
      />
    </Fragment>

  );

}


export default ProjectBoard;


Below is where the Modal component is defined:

import React, { Fragment, useState, useRef, useEffect, useCallback } from 'react';
import ReactDOM from 'react-dom';
import PropTypes from 'prop-types';

import useOnOutsideClick from '../../hooks/onOutsideClick';
import useOnEscapeKeyDown from '../../hooks/onEscapeKeyDown';


const propTypes = {
  className: PropTypes.string,
  testid: PropTypes.string,
  variant: PropTypes.oneOf(['center', 'aside']),
  width: PropTypes.number,
  withCloseIcon: PropTypes.bool,
  isOpen: PropTypes.bool,
  onClose: PropTypes.func,
  renderLink: PropTypes.func,
  renderContent: PropTypes.func.isRequired,
};

const defaultProps = {
  className: undefined,
  testid: 'modal',
  variant: 'center',
  width: 600,
  withCloseIcon: true,
  isOpen: undefined,
  onClose: () => {},
  renderLink: () => {},
};

const Modal = ({
  className,
  testid,
  variant,
  width,
  withCloseIcon,
  isOpen: propsIsOpen,  // confusion 3: what does it mean, x:y ? 
  onClose: tellParentToClose,
  renderLink,
  renderContent,
}) => {
  console.log('---- propsIsOpen: ', propsIsOpen, typeof(propsIsOpen))
  const [stateIsOpen, setStateOpen] = useState(false);
  const isControlled = typeof propsIsOpen === 'boolean';
  const isOpen = isControlled ? propsIsOpen : stateIsOpen; // confusion 2: if isOpen is defined here, why even bother to pass a prop named as isOpen ?? 
  
  const $modalRef = useRef();
  const $clickableOverlayRef = useRef();

  const closeModal = useCallback(() => {
    if (!isControlled) {
      setStateOpen(false);
    } else {
      tellParentToClose();
    }
  }, [isControlled, tellParentToClose]);

  useOnOutsideClick($modalRef, isOpen, closeModal, $clickableOverlayRef);
  useOnEscapeKeyDown(isOpen, closeModal);

  useEffect(() => {

    console.log('Modal renderContent: ', renderContent)

    document.body.style.overflow = 'hidden';

    return () => {
      document.body.style.overflow = 'visible';
    };
  }, [isOpen]);

  return (
    <Fragment>
      {!isControlled && renderLink({ open: () => setStateOpen(true) })}

      {isOpen &&
        ReactDOM.createPortal(
          <ScrollOverlay>
            <ClickableOverlay variant={variant} ref={$clickableOverlayRef}>
                ... some code ...
            </ClickableOverlay>
          </ScrollOverlay>,
          $root,
        )}
    </Fragment>
  );
};

const $root = document.getElementById('root');

Modal.propTypes = propTypes;
Modal.defaultProps = defaultProps;

export default Modal;

The above code segments works for sure. I just cannot get my head around with:

confusion 1:the variable isOpen is not defined anywhere before it is pass as a prop to the Modal component

confusion 2: isOpen is defined within the Modal component as a "fresh new variable", why even bother to pass a prop named as isOpen to the Modal component in the first place??

confusion 3: what does x:y mean in the input for a component? i.e. isOpen: propsIsOpen,

Kid_Learning_C
  • 2,605
  • 4
  • 39
  • 71

3 Answers3

2

In the place you've flagged "confusion1", isOpen isn't a variable, it's a property name. Using just a property name without a value (just isOpen, not isOpen="..." or isOpen={...}) means it's a boolean property (like checked on HTML checkboxes). If you specify it, the value of the property is true. If you don't specify it, the value of the property is undefined (as always with unspecified properties).

Your confusion 2 and 3 are the same confusion: When destructuring, x: y means "take the property named x but put it in a variable/constant called y instead. (Basically, renaming it.) So the destructuring in Modal is copying the isOpen property it received into a parameter called propsIsOpen. That way, the code in the component can declare an isOpen variable with a slightly-adjusted value.

Here's an example of a boolean property:

function Example({theProp}) {
    return <div>
        <code>typeof theProp = {typeof theProp},
        theProp = {JSON.stringify(theProp)}</code>
    </div>;
}

ReactDOM.render(
    <div>
        <div><code>&lt;Example theProp/&gt;</code> :</div>
        <Example theProp />
        <div><code>&lt;Example /&gt;</code> :</div>
        <Example />
    </div>,
    document.getElementById("root")
);
<div id="root"></div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

Here's an example of renaming destructuring:

const obj = {
    b: 42,
};
const {b: a} = obj;
console.log(a);
T.J. Crowder
  • 1,031,962
  • 187
  • 1,923
  • 1,875
1

// confusion 3 - isOpen: propsIsOpen means const propsIsOpen = isOpen

// confusion 2 - it's actually a bad practice to redeclare constants like this, so basically it creates a new variable based on isOpen from props, if it exists there, or stateIsOpen otherwise

//confusion 1 - its done to make component optionally controllable from parent - if you pass isOpen - your parent component can control if your modal isOpen, otherwise it's controlled from within the modal itself

Nikita Mazur
  • 1,602
  • 1
  • 5
  • 14
0

Confusion 1:

<Modal
 isOpen  // confusion 1: this variable is not defined anywhere!! 
 testid="modal:issue-details"

Anything that's not defined or explicitly assigned value is boolean and it takes the default value as "true". So here you are passing a prop named isOpen as true to the Modal component. You would specify as isOpen={false} otherwise.

Ref:- https://reactjs.org/docs/jsx-in-depth.html#props-default-to-true

Confusion 3:

isOpen: propsIsOpen,  // confusion 3: what does it mean, x:y ? 

You need to research ES6 destructuring. To understand it the easy way, lets see an example through named exports below

Let's say you have a function called "doThis" from "someModule"

you can import it as

import { doThis } from "someModule";
         or
import { doThis as doIt } from "someModule"

in a similar way, de-structuring allows you to name a predefined variable to a name of your choice.

That's what "isOpen: propsIsOpen" is all about. You want to refer to isOpen as propIsOpen for readability or clean code or whatever the developer's reason/ preference is.

Confusion 2:

confusion 2: if isOpen is defined here, why even bother to pass a prop named as isOpen ?? 

Well, they have decided to use another variable in the modal component to receive the incoming prop (isOpen) value in order to do some conditional checks. Unfortunately, they've used the same name isOpen, so it has caused confusion.

const isModalOpen = isControlled ? propsIsOpen : stateIsOpen;

Naming it to something else like isModalOpen could have helped you avoid confusion number 2.

dee
  • 2,244
  • 3
  • 13
  • 33
  • Thank you. On confusion 1, The answer given by T.J. Crowder above states that if `isOpen` is not specified, the value is `undefined`. This is consistent from what I printed. Are you sure the default value is `true`? – Kid_Learning_C Jul 07 '21 at 11:07
  • 2
    Props Default to “True” If you pass no value for a prop, it defaults to true. These two JSX expressions are equivalent: Ref:- https://reactjs.org/docs/jsx-in-depth.html#boolean-attributes – dee Jul 07 '21 at 11:10
  • Yep, you are listening to isOpen in useEffect hook, you could try console.logging there. Try placing the log elsewhere, it's probably logging it before the value is available. I've edited my answer with useful links wherever needed. Also, accept my answer if it helped. Thanks :) – dee Jul 07 '21 at 11:47
  • Named imports are **not** destructuring, they're only vaguely superficially similar. You have to have `as` to rename a named export (instead of `:` with destructuring), there are no defaults, there's no nesting, etc. They're different things used in different ways with different semantics. @Kid_Learning_C - Beware that the `import` examples above are not at all related to your question. – T.J. Crowder Jul 07 '21 at 15:31
  • @T.J.Crowder Yes I agree, I gave that as an example for easy understanding. I'd edited my answer on that part a bit stating it as example through named exports. – dee Jul 08 '21 at 07:30
  • @deechris27 - Why bring something completely unrelated into it at all? Just use a destructuring example. – T.J. Crowder Jul 08 '21 at 07:48
  • @T.J.Crowder its not entirely unrelated, you said its superficially similar and now completely unrelated contradicts yourself. https://stackoverflow.com/a/43987935/8884899 –  Jul 08 '21 at 08:02
  • @shakthi - You had me worried there! :-D (Because I've had to fix old answers sometimes.) But there's no contradiction at all. From my answer there: *"**Notice that imports/exports have syntax and semantics that are completely different from those of object literals / object patterns.** The only common thing is that both use curly braces, and their shorthand representations (with only identifier names and commas) are indistinguishable."* The above is using renaming, which is completely different in destructuring (object patterns) and imports. – T.J. Crowder Jul 08 '21 at 08:13
  • what is the equivalent of ES6 import { something as aThing } from 'SomeWhere' in commonJS syntax? I believe it is const { something: aThing } = require('SomeWhere'); So it may be covered under different topics in a book but it really helps understand the above problem and it is definitely not unrelated. –  Jul 08 '21 at 08:35
  • @sha - (If you don't @, no one but the answer author gets notified of the comment.) CommonJS "modules" are just objects, so sure, if you want to destructure that object with renaming, you use destructuring-with-renaming syntax. The answer brings in ESM syntax, which is not CommonJS, and is ***different*** from destructuring. It just makes no sense to veer left like that when a simple destructuring example is so much more on-point. It's basic education theory: don't introduce irrelevancies. Anyway, happy coding! I won't bother to respond further. – T.J. Crowder Jul 08 '21 at 12:31
  • 1
    @T.J.Crowder I value your inputs and made some changes to my answer stating that import example as just for understanding easier. Thank you so much for your write-up and continue to share knowledge. – dee Jul 08 '21 at 13:42
  • 1
    @deechris27 - You're very kind. Thanks to you for doing the same. We may disagree on some details, but the impetus to help people is clearly shared. :-) – T.J. Crowder Jul 08 '21 at 13:52