【问题标题】:Preventing state item in all components from changing with useContext()?使用useContext() 防止所有组件中的状态项发生变化?
【发布时间】:2021-02-08 22:41:15
【问题描述】:

我正在尝试使用 React-Native 中的 useContext() 来控制 2 个组件的状态。我有一张在屏幕上呈现的卡片平面图。每张卡片都有一个可按下的“感兴趣”图标。如果我点击一张卡片,它会显示卡片的详细信息以及可按下的“感兴趣”图标。两者都应该共享相同的被按下状态,并且还向后端发送一个 'interested bool。

现在,如果我在一张卡片上按“感兴趣”,那么所有卡片都会被按下。与详情页的内部图标相同。显然,上下文提供者中的“感兴趣”状态是扩大和改变所有这些。如何过滤掉状态变化?

这是从平面列表中呈现的卡片组件:

import React, { useState, useEffect, useContext } from 'react';
import { Image, View, Text, TouchableOpacity } from 'react-native';
import { localDate } from 'utils/formatDate';
import PropTypes from 'prop-types';
import { ConcertContext } from '../../../context/ConcertContextProvider';
import LocationIcon from '../../../assets/images/locationIcon.svg';
import InterestedFilledIcon from '../../../assets/images/interested-filled.svg';
import InterestedEmptyIcon from '../../../assets/images/interested-empty.svg';
import ShareIcon from '../../../assets/images/share.svg';
import styles from './styles';

export default function ConcertCard({
  gotoConcertPage,
  artist,
  cover,
  concertCity,
  scheduledAt,
  description,
  concertObj,
}) {
  const [clickInterest, setClickInterest] = useState(concertObj.is_interested);
  const { interested, setInterested, concertInterest } = useContext(ConcertContext);

  const handleInterest = () => {
    setInterested(prevState => !prevState);
    concertInterest(concertObj);
  };

  useEffect(() => {
    setClickInterest(interested);
  }, [interested]);

  return (
    <TouchableOpacity onPress={() => gotoConcertPage(concertObj)}>
      <View style={styles.card}>
        <View style={styles.cardContent}>
          <View style={styles.cropped}>
            <Image style={styles.image} source={{ url: cover.url }} />
          </View>
          <View style={styles.textWrapper}>
            <Text style={styles.date}>{localDate(scheduledAt, 'MMM D, h:mm A z')}</Text>
            <View style={styles.locationWrapper}>
              <LocationIcon style={styles.locationIcon} />
              <Text style={styles.location}>{concertCity.name}</Text>
              <Text style={styles.location}>{concertCity.address}</Text>
            </View>
            <Text style={styles.name}>{artist.name}</Text>
            <Text style={styles.description}>{description}</Text>
          </View>
          <View style={styles.border} />
        </View>
        <View style={styles.icons}>
          <View style={styles.sharedIcon}>
            <ShareIcon />
          </View>
          {!clickInterest ? (
            <InterestedEmptyIcon onPress={handleInterest} />
          ) : (
            <InterestedFilledIcon onPress={handleInterest} />
          )}
        </View>
      </View>
    </TouchableOpacity>
  );
}

ConcertCard.propTypes = {
  gotoConcertPage: PropTypes.func,
  artist: PropTypes.object,
  cover: PropTypes.object,
  concertCity: PropTypes.object,
  scheduledAt: PropTypes.string,
  description: PropTypes.string,
  concertObj: PropTypes.object,
};

这里是卡详情页面:

import React, { useState, useEffect, useContext } from 'react';
import { Text, View, Image, ImageBackground, ScrollView, FlatList } from 'react-native';
import PropTypes from 'prop-types';
import Button from 'components/button';
import { localDate } from 'utils/formatDate';
import { ConcertContext } from '../../context/ConcertContextProvider';
import LocationIcon from '../../assets/images/locationIconTwo.svg';
import LittleFriendIcon from '../../assets/images/little-friend.svg';
import Star from '../../assets/images/Star.svg';
import QuestionMarkIcon from '../../assets/images/question-mark.svg';
import ShareIcon from '../../assets/images/ShareIconLarge.svg';

import styles from './concertStyles';

export default function ConcertPageScreen({ navigation, route }) {
  const {
    concertObj,
    name,
    artist,
    scheduled_at,
    interests,
    concert_city,
    other_cities,
    description,
    ticket_base_price,
  } = route.params;

  const { interested, setInterested, concertInterest } = useContext(ConcertContext);

  const formatLocation = city => {
    return city.slice(0, city.length - 5);
  };

  const handleInterest = () => {
    setInterested(prevState => !prevState);
    concertInterest(concertObj);
  };

  const buyTicket = concertObj => {
    console.log('Buy ticked: ', concertObj.id);
  };

  return (
    <>
      <View style={styles.wrapper}>
        <ImageBackground
          style={styles.imageBackground}
          imageStyle={{ opacity: 0.3 }}
          source={{ url: artist.media }}
        />
        <ScrollView vertical>
          <View style={styles.headerWrapper}>
            <Text style={styles.headerTitle}>{name}</Text>
            <Text style={styles.smallText}>{artist.name}</Text>
            <Text style={styles.date}>{localDate(scheduled_at, 'MMM D, h:mm A z')}</Text>
            <View style={styles.locationWrapper}>
              <LocationIcon style={styles.locationIcon} />

              <Text style={styles.location}>{concert_city.name}</Text>
              <Text style={styles.location}>{concert_city.address}</Text>
            </View>
            <Text style={styles.description}>{description}</Text>
            <View style={styles.interestedWrapper}>
              <LittleFriendIcon style={styles.littleMan} />
              <Text style={styles.interested}>{interests} Interested</Text>
            </View>
            <View
              style={{
                borderTopColor: '#DADADA',
                borderTopWidth: 0.2,
                borderStyle: 'solid',
                alignSelf: 'center',
                height: 5,
                marginTop: 32,
                marginBottom: 28.5,
              }}
            />
            <View style={styles.tableWrapper}>
              <View style={styles.tableHeaderWrapper}>
                <Text style={styles.headerTitle}>DATES</Text>
                <Text style={styles.headerTitle}>LOCATIONS</Text>
              </View>
              <View>
                <FlatList
                  data={other_cities}
                  initialNumToRender={1}
                  renderItem={({ item }) => (
                    <View style={styles.tableItemsWrapper}>
                      <Text style={styles.dateItem}>
                        {localDate(item.date, 'MMM DD - h:mm A z')}
                      </Text>
                      <Text style={styles.locationItem}>{formatLocation(item.city)}</Text>
                    </View>
                  )}
                  concertCityId={item => `item${item.concert_city_id}`}
                  concertScheduleId={item => `item${item.concert_schedule_id}`}
                />
              </View>
            </View>
            <View style={styles.iconWrapper}>
              <View style={{ backgroundColor: '#292929', width: 44, height: 44, borderRadius: 30 }}>
                <ShareIcon style={styles.shareIcon} />
              </View>

              {!interested ? (
                <View
                  style={{ backgroundColor: '#292929', width: 44, height: 44, borderRadius: 30 }}>
                  <Star style={styles.starIcon} onPress={handleInterest} />
                </View>
              ) : (
                <View
                  style={{ backgroundColor: '#007AFF', width: 44, height: 44, borderRadius: 30 }}>
                  <Star style={styles.starIcon} onPress={handleInterest} />
                </View>
              )}
              <Image style={styles.roundAvatar} source={{ url: artist.media }} />
            </View>
            <View style={styles.iconWrapper}>
              <Text style={styles.shareIconText}>Share</Text>
              <Text style={styles.interestedIconText}>I'm Interested</Text>
              <Text style={styles.artistIconText}>Add Artist</Text>
            </View>
            <View style={styles.ticketWrapper}>
              <Text style={styles.ticketPrice}>TICKET PRICES</Text>
              <QuestionMarkIcon style={styles.ticketPrice} />
            </View>
            <View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
              <Text style={styles.ticketLocation}>In-location</Text>
              <Text style={styles.ticketLocation}>$ {ticket_base_price}</Text>
            </View>
            <View
              style={{
                borderTopColor: '#DADADA',
                borderTopWidth: 0.5,
                borderStyle: 'solid',
                height: 20,
                marginTop: 32,
                marginBottom: 28.5,
              }}
            />
            <View style={styles.footerWrapper}>
              <Text style={styles.headerTitle}>TICKET GUIDE</Text>
              <Text style={styles.ticketDescription}>
                This ticket is for a livestream concert. Sign In at www.colortv.me on your browser
                to watch on our TV or laptop. On the go? Watch from the COLOR TV mobile app.
              </Text>
            </View>
          </View>
          <Button
            rightIcon={false}
            text="Buy Ticket"
            style={styles.button}
            onPress={() => buyTicket(concertObj)}
          />
        </ScrollView>
      </View>
    </>
  );
}

ConcertPageScreen.propTypes = {
  navigation: PropTypes.object,
  route: PropTypes.object,
};

这里是上下文提供者:

import React, { createContext, useState } from 'react';
import { concertInterestApi } from 'utils/apiRoutes';
import useFetcher from 'hooks/useFetcher';
import parseError from '../utils/parseError';

export const ConcertContext = createContext(null);

const ConcertContextProvider = ({ children }) => {
  const { isLoading, error, fetcher } = useFetcher();
  const [interested, setInterested] = useState([]);

  const concertInterest = async concertObj => {
    try {
      await fetcher({
        url: concertInterestApi(concertObj.id),
        method: concertObj.is_interested ? 'DELETE' : 'POST',
      });
    } catch ({ response }) {
      throw parseError(response);
    }
  };

  return (
    <ConcertContext.Provider value={{ interested, setInterested, concertInterest }}>
      {children}
    </ConcertContext.Provider>
  );
};

export default ConcertContextProvider;

也许我根本没有正确接线,欢迎任何建议来管理状态。

【问题讨论】:

    标签: reactjs react-native state use-context


    【解决方案1】:

    您对自己上下文的形状感到困惑。在您的Provider 中,interestedarray(我不确定是什么),setInterested 是替换该数组的函数。考虑到这一点,希望你能看到这个问题:

      const { interested, setInterested, concertInterest } = useContext(ConcertContext);
    
      const handleInterest = () => {
        setInterested(prevState => !prevState);
        concertInterest(concertObj);
      };
    

    interestedarray 时,您将interested 视为boolean。我想是这个用户感兴趣的所有音乐会吧?或者可能是对这场音乐会感兴趣的所有用户 ID 的数组?或者useState([]) 是一个错误,它总是意味着是一个boolean

    无论哪种方式,我都建议将共享逻辑从ConcertCardConcertPageScreen 移动到一个自定义挂钩中,该挂钩使用您的上下文并返回一个onClickInterested 处理函数。您的钩子可以将音乐会 ID 和/或用户 ID 作为参数。

    【讨论】:

    • 是的,它总是应该是一个布尔值。
    猜你喜欢
    • 2022-01-25
    • 1970-01-01
    • 2018-09-16
    • 2021-10-11
    • 2019-11-27
    • 2015-01-10
    • 2020-03-14
    • 2020-02-14
    • 1970-01-01
    相关资源
    最近更新 更多