如何根据id更新状态?

how to update state according to id?

我在每张卡片上都有一个 touchableopacity,我想将 expand 的状态设置为 true,但我想根据 id 来做,这样只有一个状态会改变,不知道如何使用 map( )?

我的代码:

import React, {useState, useEffect} from 'react';
import {
  SafeAreaView,
  Text,
  Image,
  ScrollView,
  TouchableOpacity,
  View,
} from 'react-native';
import axios from 'axios';
import {ROOT} from '../../../../ApiUrl';

import Icon from 'react-native-vector-icons/FontAwesome';
export default function VaccinationListScreen(props) {
  const [expand, setExpand] = useState(false);
  const [data, setData] = useState('');

  let id = props.route.params.id;
  const getData = () => {
  
    let url = `some url`;
    console.log('bbb');

    axios
      .get(url)
      .then(function (res) {
        console.log(res.data.content);
        setData(res.data.content);
      })
      .catch(function (err) {
        console.log(err);
      });
  };
  useEffect(() => {
    getData();
  }, []);
  return (
    <SafeAreaView>
      <ScrollView>
      
        <TouchableOpacity style={{padding: 10}} onPress={()=>setExpand(true)}>
          {data != undefined &&
            data != null &&
            data.map((item) => {
              return (
                <View
                  style={{
                    padding: 10,
                    backgroundColor: '#fff',
                    elevation: 3,
                    margin: '2%',
                    borderRadius: 5,
                  }}
                  key={item.id}>
                  <View style={{alignItems: 'flex-end'}}>
                    <Text style={{color: 'grey', fontSize: 12}}>
                      {item.display_date}
                    </Text>
                  </View>
                  <View style={{flexDirection: 'row'}}>
                    <View>
                      <Image
                        source={require('../../assets/atbirth.jpg')}
                        style={{height: 40, width: 50}}
                        resizeMode="contain"
                      />
                    </View>
                    <View style={{flex: 1}}>
                      <View style={{flexDirection: 'row', flex: 1}}>
                        <Text
                          key={item.id}
                          style={{
                            fontFamily: 'Roboto',
                            fontSize: 18,
                            fontWeight: 'bold',
                          }}>
                          {item.name}
                        </Text>
                      </View>
                      <View style={{flexDirection: 'row', width: '30%'}}>
                        {item.vaccine_list.map((i) => {
                          return (
                            <View style={{flexDirection: 'row'}}>
                              <Text
                                numberOfLines={1}
                                ellipsizeMode="tail"
                                style={{fontFamily: 'Roboto', fontSize: 15}}>
                                {i.name},
                              </Text>
                            </View>
                          );
                        })}
                      </View>
                    </View>
                  </View>
                  <View style={{alignItems: 'flex-end', marginTop: '1%'}}>
                    <View style={{flexDirection: 'row'}}>
                      <Text
                        style={{
                          color: 'red',
                          fontSize: 14,
                          fontWeight: 'bold',
                        }}>
                        {item.child_vacc_status.text}
                      </Text>
                      <Icon
                        name="chevron-up"
                        color="red"
                        size={12}
                        style={{marginTop: '1%', marginLeft: '1%'}}
                      />
                    </View>
                  </View>
                </View>
              );
            })}
        </TouchableOpacity>
      </ScrollView>
    </SafeAreaView>
  );
}

任何建议都很好,如果还需要其他任何东西以更好地理解,请让 mw 知道

我还没有测试代码是否正常工作,但您可以尝试类似的方法。您可以为项目创建单独的组件并为每个项目设置状态。

export default function VaccinationListScreen(props) {
  const [expand, setExpand] = useState(false);
  const [data, setData] = useState("");

  const VaccinationListItem = (item) => {
    const [expand, setExpand] = useState(false);

    return (
      <TouchableOpacity style={{ padding: 10 }} onPress={() => setExpand(true)}>
        <View
          style={{
            padding: 10,
            backgroundColor: "#fff",
            elevation: 3,
            margin: "2%",
            borderRadius: 5,
          }}
          key={item.id}
        >
          <View style={{ alignItems: "flex-end" }}>
            <Text style={{ color: "grey", fontSize: 12 }}>
              {item.display_date}
            </Text>
          </View>
          <View style={{ flexDirection: "row" }}>
            <View>
              <Image
                source={require("../../assets/atbirth.jpg")}
                style={{ height: 40, width: 50 }}
                resizeMode="contain"
              />
            </View>
            <View style={{ flex: 1 }}>
              <View style={{ flexDirection: "row", flex: 1 }}>
                <Text
                  key={item.id}
                  style={{
                    fontFamily: "Roboto",
                    fontSize: 18,
                    fontWeight: "bold",
                  }}
                >
                  {item.name}
                </Text>
              </View>
              <View style={{ flexDirection: "row", width: "30%" }}>
                {item.vaccine_list.map((i) => {
                  return (
                    <View style={{ flexDirection: "row" }}>
                      <Text
                        numberOfLines={1}
                        ellipsizeMode="tail"
                        style={{ fontFamily: "Roboto", fontSize: 15 }}
                      >
                        {i.name},
                      </Text>
                    </View>
                  );
                })}
              </View>
            </View>
          </View>
          <View style={{ alignItems: "flex-end", marginTop: "1%" }}>
            <View style={{ flexDirection: "row" }}>
              <Text
                style={{
                  color: "red",
                  fontSize: 14,
                  fontWeight: "bold",
                }}
              >
                {item.child_vacc_status.text}
              </Text>
              <Icon
                name="chevron-up"
                color="red"
                size={12}
                style={{ marginTop: "1%", marginLeft: "1%" }}
              />
            </View>
          </View>
        </View>
      </TouchableOpacity>
    );
  };

  return (
    <SafeAreaView>
      <ScrollView>
        {data != undefined &&
          data != null &&
          data.map((item) => {
            VaccinationListItem(item);
          })}
      </ScrollView>
    </SafeAreaView>
  );
}

通常,如果您想切换任何单个元素,您应该将其 id 存储在 expand 中(而不是布尔值),并在呈现数组时简单地检查是否有任何特定元素的 id 匹配,即 element.id === expand.当触摸任何新元素时,将其 id 弹出到那里,如果 id 已经存在,则设置为 null 以折叠。

export default function VaccinationListScreen(props) {
  const [expandId, setExpandId] = useState(null); // <-- stores null or id, initially null
  
  ...

  // Create curried handler to set/toggle expand id
  const expandHandler = (id) => () =>
    setExpandId((oldId) => (oldId === id ? null : id));

  return (
    <SafeAreaView>
      <ScrollView>
        {data &&
          data.map((item) => {
            return (
              <View
                ...
                key={item.id}
              >
                <TouchableOpacity
                  style={{ padding: 10 }}
                  onPress={expandHandler(item.id)} // <-- attach callback and pass id
                >
                  ...
                </TouchableOpacity>
                {item.id === expandId && ( // <-- check if id match expand id
                  <ExpandComponent />
                )}
              </View>
            );
          })}
      </ScrollView>
    </SafeAreaView>
  );
}

当我查看您的代码时,<TouchableOpacity> 一次包装您所有的卡片,而不是每套卡片。如果您以这种方式实现您的代码(如果不是不可能的话),那么您将很难引用每个卡片 ID 并根据卡片 ID 将扩展状态设置为 true。

我的建议是将 <TouchableOpacity> 包含到 map() 函数嵌套中,以便于引用每个卡片函数。

我重现了这个特定问题并实施了一个解决方案,在该解决方案中,我能够根据每个卡片 ID 将展开状态设置为 true。

您可以单击沙盒 link 观看演示。

https://codesandbox.io/s/accordingtoid-4px1w

沙盒中的代码:

import React, { useState, useEffect } from "react";
import {
  SafeAreaView,
  Text,
  Image,
  TouchableOpacity,
  View
} from "react-native";
// import axios from 'axios';
// import {ROOT} from '../../../../ApiUrl';
// import Icon from "react-native-vector-icons/FontAwesome";

export default function VaccinationListScreen(props) {
  const [expand, setExpand] = useState({});
  const [data, setData] = useState([]);

  // let id = props.route.params.id;
  // const getData = () => {

  //   let url = `some url`;
  //   console.log('bbb');

  //   axios
  //     .get(url)
  //     .then(function (res) {
  //       console.log(res.data.content);
  //       setData(res.data.content);
  //     })
  //     .catch(function (err) {
  //       console.log(err);
  //     });
  // };
  // useEffect(() => {
  //   getData();
  // }, []);

  useEffect(() => {
    // In order to simulate and reproduce the problem
    // Assume that these are the data that you fetch from an API
    const dataContent = [
      {
        id: 1,
        name: "At Birth",
        display_date: "02 May - 08 May 16",
        vaccine_list: [
          { name: "BCG" },
          { name: "Hepatitis B" },
          { name: "OPV 0" }
        ],
        child_vacc_status: { text: "Missed" }
      },
      {
        id: 2,
        name: "At 6 Weeks",
        display_date: "02 May - 08 May 16",
        vaccine_list: [
          { name: "IPV" },
          { name: "PCV" },
          { name: "Hepatitis b" },
          { name: "DTP" },
          { name: "HiB" },
          { name: "Rotavirus" }
        ],
        child_vacc_status: { text: "Missed" }
      }
    ];

    setData(dataContent);
  }, []);

  function handleOnPress(id) {
    setExpand((prev) => {
      let toggleId;
      if (prev[id]) {
        toggleId = { [id]: false };
      } else {
        toggleId = { [id]: true };
      }
      return { ...toggleId };
    });
  }

  useEffect(() => {
    console.log(expand); // check console to see the value
  }, [expand]);

  return (
    <SafeAreaView>
      {data !== undefined &&
        data !== null &&
        data.map((item) => {
          return (
            <TouchableOpacity
              key={item.id}
              style={{
                padding: 10
              }}
              onPress={() => handleOnPress(item.id)}
            >
              <View
                style={{
                  padding: 10,
                  backgroundColor: expand[item.id] ? "lightgrey" : "#fff",
                  elevation: 3,
                  margin: "2%",
                  borderRadius: 5
                }}
              >
                <View style={{ alignItems: "flex-end" }}>
                  <Text style={{ color: "grey", fontSize: 12 }}>
                    {item.display_date}
                  </Text>
                </View>
                <View style={{ flexDirection: "row" }}>
                  <View>
                    <Image
                      // source={require('../../assets/atbirth.jpg')}
                      style={{ height: 40, width: 50 }}
                      resizeMode="contain"
                    />
                  </View>
                  <View style={{ flex: 1 }}>
                    <View style={{ flexDirection: "row", flex: 1 }}>
                      <Text
                        key={item.id}
                        style={{
                          fontFamily: "Roboto",
                          fontSize: 18,
                          fontWeight: "bold"
                        }}
                      >
                        {item.name}
                      </Text>
                    </View>
                    <View style={{ flexDirection: "row", width: "30%" }}>
                      {item.vaccine_list.map((item, i) => {
                        return (
                          <View key={i} style={{ flexDirection: "row" }}>
                            <Text
                              numberOfLines={1}
                              ellipsizeMode="tail"
                              style={{ fontFamily: "Roboto", fontSize: 15 }}
                            >
                              {item.name},
                            </Text>
                          </View>
                        );
                      })}
                    </View>
                  </View>
                </View>
                <View style={{ alignItems: "flex-end", marginTop: "1%" }}>
                  <View style={{ flexDirection: "row" }}>
                    <Text
                      style={{
                        color: "red",
                        fontSize: 14,
                        fontWeight: "bold"
                      }}
                    >
                      {item.child_vacc_status.text}
                    </Text>
                  </View>
                </View>
              </View>
            </TouchableOpacity>
          );
        })}
    </SafeAreaView>
  );
}