4

I started learning React not so long ago. Decided to make some kind of "life checklist" as one of my beginner projects. I have been using Functional Components in the core.

FYI:

  1. I have data.js as an array of objects where "action", "emoji" and unique ID are stored.
  2. I import it into my App.js.
const App = () => {
  //Looping over data
  const items = data.map((item) => {
    return (
      <ChecklistItem action={item.action} emoji={item.emoji} key={item.id} />
    );
  });

  return (
    <>
      <GlobalStyle />
      <StyledHeading>Life Checklist</StyledHeading>
      <StyledApp>{items}</StyledApp>
      <h2>Overall number: {data.length}</h2>
    </>
  );
};

export default App;

Here is my <ChecklistItem/> component:

const ChecklistItem = ({ action, emoji }) => {
  //State
  const [isActive, setIsActive] = useState(false);

  //Event Handlers
  const changeHandler = () => {
    setIsActive(!isActive);
  };

  return (
    <StyledChecklistItem isActive={isActive}>
      <input type="checkbox" checked={isActive} onChange={changeHandler} />
      <StyledEmoji role="img">{emoji}</StyledEmoji>
      <StyledCaption>{action}</StyledCaption>
    </StyledChecklistItem>
  );
};

export default ChecklistItem;

I would be satisfied with the functionality so far, but I need to show how many "active" checklist items were chosen in the parent <App/> component like "You have chosen X items out of {data.length}. How can I achieve this?

I assume that I need to lift the state up, but cannot understand how to implement this properly yet.

1
  • 1
    Create the state inside the App and pass a function to ChecklistItem instead of using state within the ChecklistItem. That way you collect all the states in one location and you can do whatever you want. Commented Dec 8, 2020 at 16:57

3 Answers 3

3

You can do that by simply creating a state for storing this particular count of active items.

To do that, you would need to update your <App/> component to something like this

const App = () => {
  const [activeItemsCount, setActiveItemsCount] = useState(0);

  //Looping over data
  const items = data.map((item, index) => {
    return (
      <ChecklistItem
        key={index}
        action={item.action}
        emoji={item.emoji}
        setActiveItemsCount={setActiveItemsCount}
      />
    );
  });

  return (
    <>
      <h1>Life Checklist</h1>
      <div>{items}</div>
      <div>Active {activeItemsCount} </div>
      <h2>Overall number: {data.length}</h2>
    </>
  );
};

export default App;

And then in your <ChecklistItem /> component, you would need to accept that setActiveItemsCount function so that you can change the state of the activeItemsCount.

import React, { useState, useEffect } from "react";

const ChecklistItem = ({ action, emoji, setActiveItemsCount }) => {
  const [isActive, setIsActive] = useState(false);

  const changeHandler = () => {
    setIsActive(!isActive);
  };

  useEffect(() => {
    if (!isActive) {
      setActiveItemsCount((prevCount) => {
        if (prevCount !== 0) {
          return prevCount - 1;
        }

        return prevCount;
      });
    }

    if (isActive) {
      setActiveItemsCount((prevCount) => prevCount + 1);
    }
  }, [isActive, setActiveItemsCount]);

  return <input type="checkbox" checked={isActive} onChange={changeHandler} />;
};

export default ChecklistItem;

By using the useEffect and the checks for isActive and 0 value, you can nicely increment or decrement the active count number by pressing the checkboxes.

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

Comments

2

How about this?

const data = [
   { action: '1', emoji: '1', id: 1 },
   { action: '2', emoji: '2', id: 2 },
   { action: '3', emoji: '3', id: 3 },
];

const ChecklistItem = ({ action, emoji, isActive, changeHandler }) => {
   return (
      <div isActive={isActive}>
         <input type="checkbox" checked={isActive} onChange={changeHandler} />
         <div>{emoji}</div>
         <div>{action}</div>
      </div>
   );
};

const PageContainer = () => {
   const [checkedItemIds, setCheckedItemIds] = useState([]);

   function changeHandler(itemId) {
      if (checkedItemIds.indexOf(itemId) > -1) {
         setCheckedItemIds((prev) => prev.filter((i) => i !== itemId));
      } else {
         setCheckedItemIds((prev) => [...prev, itemId]);
      }
   }

   const items = data.map((item) => {
      const isActive = checkedItemIds.indexOf(item.id) > -1;
      return (
         <ChecklistItem
            isActive={isActive}
            changeHandler={() => changeHandler(item.id)}
            action={item.action}
            emoji={item.emoji}
            key={item.id}
         />
      );
   });
   return (
      <div className="bg-gray-100">
         <div>{items}</div>
         <h2>
            You have chosen {checkedItemIds.length} items out of {data.length}
         </h2>
      </div>
   );
};

Comments

0

When data is used by a child component, but the parent needs to be aware of it for various reasons, that should be state in the parent component. That state is then handed to the child as props.

One way to do this would be to initialize your parent component with a piece of state that was an array of boolean values all initialized to false. Map that state into the checkbox components themselves and hand isActive as a prop based on that boolean value. You should then also hand the children a function of the parent that will change the state of the boolean value at a certain index of that array.

Here's a bit of a contrived example:

  // Parent.tsx

  const [checkBoxes, setCheckboxes] = useState(data.map(data => ({
    id: data.id,
    action: data.action,
    emoji: data.emoji
    isActive: false,
  })));

  const handleCheckedChange = (i) => {
    setCheckboxes(checkBoxes => {
      checkBoxes[i].isActive = !checkBoxes[i].isActive;
      return checkBoxes;
    })
  } 

  return(
    checkBoxes.map((item, i) => 
      <ChecklistItem 
        action={item.action} 
        emoji={item.emoji} 
        key={item.id} 
        index={i} 
        isActive={item.isActive} 
        handleChange={handleCheckedChange} 
     />
    )
  );


  // CheckListItem.tsx
  const CheckListItem = ({ action, emoji, index, isActive, handleChange }) => (
      <StyledChecklistItem isActive={isActive}>
        <input type="checkbox" checked={isActive} onChange={() => handleChange(index)} />
        <StyledEmoji role="img">{emoji}</StyledEmoji>
        <StyledCaption>{action}</StyledCaption>
      </StyledChecklistItem>
  )

Comments

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.