0

i stumbled into an issue i cant solve, i have an object 'customerDraft' which has nested object in it. i want to render every field plus the fields which are inside of 'customerDraft.metadata'.

my component looks like this:

const CustomerDetailEditModal = (props) => {
  const {
    open,
    setOpen,
    customerDraft,
    customerProfileDraft,
    setDraftCustomer,
    setDraftProfile,
    onUpdate
  } = props;

  const classes = useStyles();
  const dispatch = useDispatch();

  const [isPasswordHidden, setIsPasswordHidden] = useState(true);
  // const [attributes, setAttributes] = useState({});

  const projectId = useSelector(({ project }) => project.currentProject._id);
  const generatedPassword = useSelector(({ customer }) => customer.password);

  const isCurrentProjectCapstone = projectId === '4387564328756435';

  const onModalCancel = () => {
    setOpen(false);
    if (isCurrentProjectCapstone) {
      dispatch(removeItemFromCustomerDraftAction('password'));
    }
  };

  const generatePassword = () => {
    dispatch(getGeneratedPassword());
  };

  useEffect(() => {
    if (!generatedPassword) return;
    setDraftCustomer({
      ...customerDraft,
      password: generatedPassword
    });

    // eslint-disable-next-line
  }, [generatedPassword]);

  console.log(customerDraft);

  return (
    <div>
      <Modal
        bodyStyle={{
          fontSize: '12px',
          height: 500,
          margin: '0 auto'
        }}
        centered
        footer={
          <div
            style={{
              display: 'flex',
              justifyContent: 'flex-end'
            }}>
            <CButton
              htmlType="submit"
              onClick={(e) => {
                setOpen(false);
                e.preventDefault();
              }}
              size="large"
              type="secondary">
              Cancel
            </CButton>
            <CButton
              htmlType="submit"
              onClick={onUpdate}
              size="large"
              type="primary"
              // disabled={!isSaveEnabled}
            >
              Save
            </CButton>
          </div>
        }
        onCancel={onModalCancel}
        title={
          <span
            style={{
              fontSize: '24px',
              fontWeight: 700,
              lineHeight: '24px'
            }}>
            Edit User
          </span>
        }
        visible={open}
        width={customerProfileDraft ? 770 : 385}>
        <form className={classes.form} id="customer-edit-form">
          <div className={classes.wrapperDiv}>
            {Object.entries(customerDraft).map((item, i) => {
              if (customerDraft.fullName) {
                if (restrictedData.includes(item[0] || item[0].toLowerCase().includes('id'))) {
                  return false;
                }
              }
              if (restrictedData.includes(item[0]) || item[0].toLowerCase().includes('id')) {
                return false;
              }
              return (
                <CStandardInput
                  key={i}
                  allowClear
                  defaultValue={item[1]}
                  disableUnderline
                  formclasses={{ root: classes.root }}
                  htmlFor={`standard-customer-edit-${item[0]}`}
                  id={`standard-customer-edit-${item[0]}`}
                  label={item[0]}
                  onChange={(event) => {
                    setDraftCustomer({
                      ...customerDraft,
                      fullName: event.target.value
                    });
                    setDraftProfile({
                      ...customerProfileDraft,
                      fullName: event.target.value
                    });
                  }}
                  size="large"
                />
              );
            })}
            {isCurrentProjectCapstone && (
              <div className={classes.passwordWrapper}>
                <CStandardInput
                  adornment={
                    <>
                      <button
                        className={classes.buttonSvg}
                        onClick={() => {
                          navigator.clipboard.writeText(customerDraft.password || '');
                        }}
                        style={{
                          marginRight: '5px'
                        }}
                        type="button">
                        <img alt="copy password" src={copyIcon} />
                      </button>
                      <button
                        className={classes.buttonSvg}
                        onClick={() => setIsPasswordHidden(!isPasswordHidden)}
                        type="button">
                        <img
                          alt="toggle password visibility"
                          src={isPasswordHidden ? crossedEyeIcon : eyeIcon}
                        />
                      </button>
                    </>
                  }
                  disableUnderline
                  formclasses={{ root: classes.root }}
                  htmlFor="standard-input-user-password"
                  id="standard-input-user-password"
                  label="Password"
                  onChange={(e) => setDraftCustomer({ ...customerDraft, password: e.target.value })}
                  size="large"
                  type={isPasswordHidden ? 'password' : 'text'}
                  value={customerDraft.password || ''}
                  width="true"
                />
                <CButton
                  onClick={generatePassword}
                  type="primary"
                  xstyle={{
                    borderRadius: '12px',
                    margin: '16px 0px 0px 16px'
                  }}>
                  Generate
                </CButton>
              </div>
            )}
          </div>
        </form>
      </Modal>
    </div>
  );
};

export default CustomerDetailEditModal;

enter image description here

notice how metdata field is rendered? i want to use recursion to output every field which metadata contains, i know recursion but what i cant seem to figure out is where should this component call itself to do it.

any help with explanation so that i can understand the answer would be much appreciated!

this is the object im iterating on:

const customerData = {
  createdAt: "2022-10-28T08:42:08.015Z",
  email: "[email protected]",
  firstName: "$$$$$$$",
  fullName: "$$$$$$",
  idNumber: "2813921321",
  isEmailVerified: true,
  isPhoneVerified: true,
  lastName: "$$$$$",
  metadata: {
    birthDate: "2000-08-19 00:00:00.000",
    gender: "Male",,
    region: "",
    status: "Adult",
    statusExtra: "Student",
  },
  phone: "######",
  project: "hlkjhkljhkjhk",
  updatedAt: "2022-11-01T10:26:32.677Z",
  username: null,
  _id: "hlkjhlkjhlkjhlkjhlkjh",
};

see metadata? currently im outputting only the fields of the main(parent) object, but i also want to output the data which is contained in the 'metadata' key using recursion.

4
  • can you simplify this to the exact issue? Just show an example object and what you want the result to be Commented Nov 7, 2022 at 15:02
  • sure, let me update post, sorry for bad question Commented Nov 7, 2022 at 15:15
  • updated the post! thanks for your time! if my question still isnt clear just say and ill try to explain in further details Commented Nov 7, 2022 at 15:23
  • If you are asking how to render a component for all of the items in an object recursively, then i suggest you simplify your question to only include the relevant details. It just takes a lot of time for answerers to dig through all the irrelevant fields, components, etc. to get you a good answer. Commented Nov 7, 2022 at 21:08

1 Answer 1

1

A solution to this could be to check if the key item[0] is "metadata". Then you could do the same as you did with the customerDraft object. Get the entries an map over them.

Note that I destructured the array you get from the .entries to make it more explicit what the variables are.

if (item[0] === "metadata") {
  const inputs = Object.entries(item[1]).map(([metaKey, metaValue]) => (
    <CStandardInput
      key={metaKey}
      allowClear
      defaultValue={metaValue}
      disableUnderline
      formclasses={{ root: classes.root }}
      htmlFor={`standard-customer-edit-${metaKey}`}
      id={`standard-customer-edit-${metaKey}`}
      label={metaKey}
      onChange={(event) => {
        setDraftCustomer({
          ...customerDraft,
          fullName: event.target.value,
        });
        setDraftProfile({
          ...customerProfileDraft,
          fullName: event.target.value,
        });
      }}
      size="large"
    />
  ));
  return <>{inputs}</>;
}

return (
  <CStandardInput
  ...

EDIT:

To support the nested data with recursion, I've created a function with returns an input for every key, value pair in the data.

You can add your extra if statements as desired

const renderInputs = (data) => {
  const inputs = Object.entries(data).map(([key, value]) => {
    if (
      typeof value === "object" &&
      !Array.isArray(value) &&
      value !== null
    ) {
      return renderInputs(value);
    }

    return (
      <CStandardInput
        key={key}
        allowClear
        defaultValue={value}
        disableUnderline
        formclasses={{ root: classes.root }}
        htmlFor={`standard-customer-edit-${key}`}
        id={`standard-customer-edit-${key}`}
        label={key}
        onChange={(event) => {
          setDraftCustomer({
            ...customerDraft,
            fullName: event.target.value,
          });
          setDraftProfile({
            ...customerProfileDraft,
            fullName: event.target.value,
          });
        }}
        size="large"
      />
    );
  });
  return inputs;
};

return <>{renderInputs(customerData)}</>;

Hope this helps you with your project!

Sign up to request clarification or add additional context in comments.

2 Comments

Thanks 🙏🏻 for your time, but im not sure thats what im looking for, what if there was another nested object? I would then have to write another condition. Thats why i wanted to use recursion. Whenever iteration encounters object it should call itself again or else return field as is.
@ikkako I've updated my answer with recursion, its a simplified version of the code

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.