Home > Enterprise >  How to properly conditionally render child component in react
How to properly conditionally render child component in react

Time:07-26

I have the following components : (AddBookPanel contains the AddBookForm)

I need to have the ability to display the form on click of the 'AddBookButton', and also have the ability to hide the form while clicking the 'x' button (img in AddBookForm component), however the component doesn't seem to load properly, what could be wrong here? What do you think is the best way to organize this?

import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';

export const AddBookPanel = () => {
  const [addBookPressed, setAddBookPressed] = useState(false);

  return (
    <div>
      <div onClick={() => setAddBookPressed(!addBookPressed)}>
        <AddBookButton />
      </div>
      <AddBookForm initialFormActive={addBookPressed} />
    </div>
  );
};
import { useState } from 'react';

interface AddBookFormProps {
  initialFormActive: boolean;
}

export const AddBookForm: React.FC<AddBookFormProps> = ({
  initialFormActive,
}) => {
  const [isFormActive, setIsFormActive] = useState(initialFormActive);

  return (
    <>
      {isFormActive && (
        <div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
          <div className="flex justify-between bg-slate-400">
            <div>
              <p className="text-4xl my-5 mx-6">Add a new Book</p>
            </div>
            <div className="h-16 w-16 my-3 mx-3">
              <button onClick={() => setIsFormActive(!isFormActive)}>
                <img
                  src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
                  alt="close-button"
                  className="object-fit"
                ></img>
              </button>
            </div>
          </div>

          <div className="flex-col">
            <div className="flex justify-between my-10 ml-5 text-xl">
              <input type="text" placeholder="book name"></input>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

CodePudding user response:

You're always rendering the AddBookForm. That means in the initial render the useState is called with false. Since you never call setIsFormActive it is now stuck as "do not render".

Just don't use the useState and use the property initialFormActive directly. And maybe rename it to isFormActive. Let the parent handle the state change.

import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';

export const AddBookPanel = () => {
  const [addBookPressed, setAddBookPressed] = useState(false);

  return (
    <div>
      <div onClick={() => setAddBookPressed(!addBookPressed)}>
        <AddBookButton />
      </div>
      <AddBookForm initialFormActive={addBookPressed} onClose={() => setAddBookPressed(false)} />
    </div>
  );
};
import { useState } from 'react';

interface AddBookFormProps {
  initialFormActive: boolean;
  onColse: () => void;
}

export const AddBookForm: React.FC<AddBookFormProps> = ({
  initialFormActive,
  onClose,
}) => {
  // const [isFormActive, setIsFormActive] = useState(initialFormActive); don't use this frozen state, allow the parent to control visibility

  return (
    <>
      {initialFormActive && (
        <div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
          <div className="flex justify-between bg-slate-400">
            <div>
              <p className="text-4xl my-5 mx-6">Add a new Book</p>
            </div>
            <div className="h-16 w-16 my-3 mx-3">
              <button onClick={() => onClose()}>
                <img
                  src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
                  alt="close-button"
                  className="object-fit"
                ></img>
              </button>
            </div>
          </div>

          <div className="flex-col">
            <div className="flex justify-between my-10 ml-5 text-xl">
              <input type="text" placeholder="book name"></input>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

CodePudding user response:

Problem with your code is in how you tied initialFormActive to inner state of AddBookForm component. When looking at useState inside AddBookForm it is clear that prop initialFormActive only affects your initial state, and when looking at your parent component I can clearly see that you passed false as value for initialFormActive prop, thus initializing state inside AddBookForm with false value(form will stay hidden). Then you expect when you click on button, and when prop value changes(becomes true), that your <AddBookForm /> will become visible, but it will not because you just used initial value(false) from prop and after that completely decoupled from prop value change.

Since you want to control visibility outside of component, then you should attach two props to AddBookForm, isVisible and toggleVisibiliy. And do something like this:

 export const AddBookPanel = () => {
  const [addBookPressed, setAddBookPressed] = useState(false);
  const toggleAddBookPressed = () => setAddBookPressed(p => !p);

  return (
    <div>
      <div onClick={() => setAddBookPressed(!addBookPressed)}>
        <AddBookButton />
      </div>
      <AddBookForm isVisible={addBookPressed} toggleVisibility={toggleAddBookPressed } />
    </div>
  );

};

export const AddBookForm: React.FC<AddBookFormProps> = ({
  isVisible, toggleVisibility
}) => {

  return (
    <>
      {isVisible&& (
        <div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
          <div className="flex justify-between bg-slate-400">
            <div>
              <p className="text-4xl my-5 mx-6">Add a new Book</p>
            </div>
            <div className="h-16 w-16 my-3 mx-3">
              <button onClick={toggleVisibility}>
                <img
                  src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
                  alt="close-button"
                  className="object-fit"
                ></img>
              </button>
            </div>
          </div>

          <div className="flex-col">
            <div className="flex justify-between my-10 ml-5 text-xl">
              <input type="text" placeholder="book name"></input>
            </div>
          </div>
        </div>
      )}
    </>
  );
};

CodePudding user response:

Try this one

import { useState } from 'react';

interface AddBookFormProps {
  initialFormActive: boolean;
}

export const AddBookForm: React.FC<AddBookFormProps> = ({
  initialFormActive,
}) => {
  const [isFormActive, setIsFormActive] = useState(initialFormActive);

  return (
    <>
      {isFormActive ? (
        <div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
          <div className="flex justify-between bg-slate-400">
            <div>
              <p className="text-4xl my-5 mx-6">Add a new Book</p>
            </div>
            <div className="h-16 w-16 my-3 mx-3">
              <button onClick={() => setIsFormActive(!isFormActive)}>
                <img
                  src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
                  alt="close-button"
                  className="object-fit"
                ></img>
              </button>
            </div>
          </div>

          <div className="flex-col">
            <div className="flex justify-between my-10 ml-5 text-xl">
              <input type="text" placeholder="book name"></input>
            </div>
          </div>
        </div>
      ):(<><h1>Solution when it is false</h1></>)}
    </>
  );
};

You need to add your JSX for true condition.

  • Related