Bruce Mathers
Bruce Mathers

Reputation: 681

map over ListItem, checkbox only to change for one row (React Native)

I'm using nativebase checkbox and I'm mapping over the ListItem, the issue is that when a user selects one row it changes the status of ALL the rows. How do I get it to only change one row and not all of them during mapping

    const [status, setStatus] = useState(false);

     {Object.entries(
                    allEquipmentList.reduce(
                        (byType, item) => ({
                            ...byType,
                            [item.type]: [...(byType[item.type] || []), item]
                        }),
                        {}
                    )
                ).map(([type, items]) =>
                    items.map((item, index) => {
    return (
                            <>
                                <ListItem onPress={() => setStatus(!status)}>
                                    <CheckBox checked={status} />
                                    <Body>
                                        <Text>{item.name}</Text>
                                    </Body>
                                </ListItem>
                            </>
                        )
                    })

Upvotes: 0

Views: 260

Answers (1)

Jorge Kunrath
Jorge Kunrath

Reputation: 1006

You have only one state, so, every checkbox are changing it. You need multiple states. How: put const [status, setStatus] = useState(false); inside ListItem and reimplement the switch logic.

You should move <CheckBox checked={status} /> inside ListItem too for it to have access for your state.

Ex:

function ListItem({children}) {
  const [status, setStatus] = useState(false);

  return (
    <ListItemInner onPress={() => setStatus(!status)}>
      <CheckBox checked={status} />
      <Body>
        <Text>{children}</Text>
      </Body>
    </ListItemInner>
  )
}


function SomeComponent(){

  // ... your code here ...

  return {Object.entries(
    allEquipmentList.reduce(
      (byType, item) => ({
        ...byType,
        [item.type]: [...(byType[item.type] || []), item]
      }),
      {}
    )
  ).map(([type, items]) => {
    return items.map((item, index) => {
      return <ListItem key={index}>{item.name}</ListItem>
    }
  })
}

Edit: "how to log what have been selected?"

This is the base code for log, but you still can't retrieve this information up in the tree

import { useEffect, useRef, useState } from "react";
import styled from "styled-components";

// just styles
const Label = styled.label`
  display: flex;
  align-items: center;
  border: 1px solid #eee;
  margin: 10px;
  cursor: pointer;
`;

function ListItem({ children }) {
  const [status, setStatus] = useState(false);

  // useRef helps you to get HTML element (in this case)
  // check this out: https://reactjs.org/docs/hooks-reference.html
  const itemRef = useRef(null);

  // useEffect allow you to see changes in your state. Just log outside could show you old states
  useEffect(() => {
    console.log(itemRef.current.innerText, status);
  }, [status]);

  return (
    <Label>
      <input
        type="checkbox"
        // let the browser handle the press, you just care about the change it made
        onChange={() => setStatus(!status)}
        checked={status}
      />
      {/* basically a getElementById */}
      <p ref={itemRef}>
        <span>{children}</span>
      </p>
    </Label>
  );
}

export default function App() {
  const demoItems = ["foo", "doo", "boo"];

  console.log("which items have been selected?");

  return (
    <div>
      {demoItems.map((item, index) => (
        <ListItem key={index}>{item}</ListItem>
      ))}
    </div>
  );
}


Edit 2: "how to access what was selected up in the tree? (aka, get the data)"

Here is the final code. Be aware, I don't think this is the best way of do it, but it works. Also, you should use some id for that, not the name. Use it as learning process or hard-test it

Codesandbox of it: https://codesandbox.io/s/so-map-over-listitem-checkbox-only-to-change-for-one-row-react-native-kp12p?file=/src/App.js:133-1829


import { useEffect, useRef, useState } from "react";
import styled from "styled-components";

const Label = styled.label`
  display: flex;
  align-items: center;
  border: 1px solid #eee;
  margin: 10px;
  cursor: pointer;
`;

function ListItem({ selected, setSelected, children }) {
  const [status, setStatus] = useState(false);
  const itemRef = useRef(null);

  // outter control, uses a higher state
  function handleChange() {
    const el = itemRef?.current?.innerText;
    const wasSelected = selected.includes(el);
    console.log("el ->", el);
    console.log("wasSelected ->", wasSelected);

    if (wasSelected) {
      setSelected((s) => s.filter((item) => item !== el));
    } else {
      setSelected((s) => [...s, el]);
    }
    // if the syntax of the state is weird to you, check it: https://stackoverflow.com/questions/42038590/when-to-use-react-setstate-callback
  }

  // just inner control, handles the visual update
  useEffect(() => {
    const el = itemRef?.current?.innerText;
    setStatus(selected.includes(el));
  }, [selected]);

  return (
    <Label>
      <input type="checkbox" onChange={handleChange} checked={status} />
      <p ref={itemRef}>
        <span>{children}</span>
      </p>
    </Label>
  );
}

export default function App() {
  const demoItems = ["foo", "doo", "boo"];

  const [selected, setSelected] = useState(["foo"]);

  useEffect(() => {
    console.log("which items have been selected?");
    console.log(selected);
  }, [selected]);

  return (
    <div>
      {demoItems.map((item, index) => (
        <ListItem key={index} selected={selected} setSelected={setSelected}>
          {item}
        </ListItem>
      ))}
    </div>
  );
}

Upvotes: 1

Related Questions