Я использую React с использованием машинописного текста с useContext и useReducer в своем приложении, но по какой-то причине щелчок не вызовет отправку

#reactjs #typescript #dispatch

Вопрос:

У меня есть два списка, отображаемых в виде карточек, которые взяты из одного начального списка объектов. Идея состоит в том, чтобы изменить значение «избранное» в каждом из элементов списка карт при нажатии на кнопку «Звезда», чтобы элементы, которые не являются избранными, отображались в списке несохраненных элементов, а избранные-в списке избранных элементов. У меня есть файл DataContext.tsx, подобный этому:

 import Data from './data.json'

let rawData: Bot[] = [...Data]
rawData.forEach( item => item.favorite = false )


export function createDataCtx<StateType, ActionType>(
    reducer: Reducer<StateType, ActionType>,
    initialState: StateType
) {
    const defaultDispatch: Dispatch<ActionType> = () => initialState
    const ctx = createContext({
        state: initialState,
        dispatch: defaultDispatch, // just to mock out the dispatch type and make it not optioanl
    })
    function Provider(props: PropsWithChildren<{}>) {
        const [state, dispatch] = useReducer<Reducer<StateType, ActionType>>(reducer, initialState)
        return <ctx.Provider value={{ state, dispatch }} {...props} />
    }
    return [ctx, Provider] as const
}

export const initialState = [...rawData]
type AppState = typeof initialState
type Action = 
    | { type: 'favorite', shortName: string }
    | { type: 'unFavorite', shortName: string }

export function reducer(state: AppState, action: Action): AppState {
    switch(action.shortName){
        case 'favorite':
            console.log('favorite action triggered!!')
            return state.map( (item: Bot) => item.shortName === action.shortName // will have to map the elements with the index when using it so we van pass the index
                ? {...item, favorite: true}
                : item
            )
        case 'unFavorite':
            console.log('unFavorite action triggered!!')
            return state.map( (item: Bot) => item.shortName === action.shortName // will have to map the elements with the index when using it so we van pass the index
                ? {...item, favorite: false}
                : item
            )
        default:
            return state
    }
}
 

Я использую своего Провайдера таким образом в компоненте приложения:

 function App() {
  const [ , DataProvider] = createDataCtx(reducer, initialState)
  return (
    <DataProvider>
      <ThemeProvider theme={Theme}>
        <AppStyles>
          <Router>
            <Header />
            <Content />
          </Router>
        </AppStyles>
      </ThemeProvider>
    </DataProvider>
  );
}
 

А затем я использую контекст в другом компоненте под названием Cards.tsx, например:

 const [ctx, DataProvider] = createDataCtx(reducer, initialState)
const DataContext = ctx

const Cards = () => {
    
    const {state, dispatch } = useContext(DataContext)
    const faveBotsList = state.filter( item => item.favorite === true)
    const unfaveBotsList = state.filter( item => item.favorite === false )

    return (
        <CardsStyles>
            <CardsRow>
                <FavTitle>Favorites</FavTitle>
            </CardsRow>
            <FavWrap>
                {faveBotsList.map((item) => (
                    <BotCard 
                        key={item.shortName} 
                        favorite={item.favorite} 
                        name={item.name} 
                        shortName={item.shortName} 
                        onStarClick={ () => dispatch({type: 'unFavorite', shortName: item.shortName}) } 
                    />
                ))}
            </FavWrap>
            <CardsWrap>
                {unfaveBotsList.map((item) => (
                    <BotCard 
                        key={item.shortName}
                        favorite={item.favorite}
                        name={item.name}
                        shortName={item.shortName}
                        onStarClick={ () => dispatch({type: 'favorite', shortName: item.shortName}) }/>
                ))}
            </CardsWrap>
        </CardsStyles>
            
    )
}
 

Вот код компонента «Бот-карта», если вы хотите его увидеть:

 const BotCard = (props: any) => {
    const { favorite, name, shortName, onStarClick } = props
    return (
        <StyledBCard className='col-sm-4 col-md-3 col-lg-2'>
            <Row>
                <FavoriteStar favorite={favorite} className='float-left' onStarClick={onStarClick} />
            </Row>
            <Row className='justify-content-center'>
                <BotImage imageSrc={TestImage} />
            </Row>
            <Row>
                <BotName>{name}</BotName>
            </Row>
            <Row>
                <BotShortName>{shortName}</BotShortName>
            </Row>
        </StyledBCard>
    )
}
 

свойство «onStarClick» передается дочернему компоненту, который передает его своему дочернему компоненту, где оно передается свойству «onClick» кнопки стилизованного компонента.
Но щелчок не вызовет отправку.
Я не вижу, что неправильно реализовано.

Комментарии:

1. Пожалуйста, также опубликуйте код для вашего BotCard компонента, похоже, он никогда не вызывает свой onStarClick метод

2. @Taxel Я только что отредактировал сообщение и добавил код BotCard компонента.

3. Во — первых: поскольку у вас уже есть dispatch от а Context -почему бы не использовать useContext в <FavoriteStar> компоненте и передавать только название в качестве реквизита? Во-вторых, я думал BotCard , что это тот, где dispatch функция фактически вызывается, все компоненты имеют отношение к тому, где dispatch / onStarClick передается/вызывается.

Ответ №1:

Я думаю, проблема в том, что вы звоните диспетчеру, когда передаете его. Это должно быть правильным способом:

 <BotCard 
  key={item.shortName}
  favorite={item.favorite}
  name={item.name}
  shortName={item.shortName}
  dispatch={dispatch}
/>
 

А затем назовите это в ребенке:

 <button
  onClick={() => dispatch({type: 'favorite', shortName: item.shortName})}
>
  A button
</button>
 

Комментарии:

1. следуя вашей идее, я передал сообщение следующим образом: « <Ключ боткарты={item.shortName} избранное={item.favorite} имя={item.name} Короткое имя={item.shortName} onStarClick={ () => { отправка({тип: «избранное», краткое имя: item.shortName}) } } /<Ключ боткарты={item.shortName} избранное={item.favorite} имя={item.name} Короткое имя={item.shortName} onStarClick= { () =>>«, а затем назвал его в дочерних компонентах следующим образом: « <Ключ боткарты={item.shortName} избранное={item.favorite} имя={item.name} Краткое имя={пункт.Краткое имя} onStarClick={ () =>><Имя класса StyledSButton={Имя класса} onClick={() => onStarClick()} <Имя класса StyledSButton={Имя класса} onClick={() =>> <Имя класса StyledSButton={Имя класса} onClick={() =>> FullStar : пустая звезда} /> ></StyledSButton> « Но это все равно не сработает. Никакой ошибки, ничего.

2. Вы все еще звоните диспетчеру, когда передаете его в реквизит… Или вы пропустили старую версию? Я заметил ошибку в ребенке, я использовал «отправка» вместо названия реквизита.

3. Я называю отправку в дочернем компоненте именем реквизита, а не «отправкой», то есть onClick={() => onStarClick()} . В родительском компоненте я передаю отправку следующим образом: onStarClick={ () => { dispatch({type: 'favorite', shortName: item.shortName}) } }

4. Я вижу, что недостаточно ясно выразился. Вы вызываете отправку в родителе таким образом, и вы не должны этого делать. То, что вы должны передать в качестве реквизита, — это просто имя «отправка» без вызова и без аргумента, например: onStarClick={отправка}. Затем вы используете реквизит в дочернем элементе, как вы сделали бы с отправкой: onClick={() => onStarClick({тип: «избранное», краткое имя: item.shortName})}.

Ответ №2:

Проблема была в моем исходном редукторе в файле «DataContext.tsx»

Мой редуктор был таким:

 type Action = 
    | { type: 'favorite', shortName: string }
    | { type: 'unFavorite', shortName: string }

export function reducer(state: AppState, action: Action): AppState {
    switch(action.shortName){
        case 'favorite':
            console.log('favorite action triggered!!')
            return state.map( (item: Bot) => item.shortName === action.shortName // will have to map the elements with the index when using it so we van pass the index
                ? {...item, favorite: true}
                : item
            )
        case 'unFavorite':
            console.log('unFavorite action triggered!!')
            return state.map( (item: Bot) => item.shortName === action.shortName // will have to map the elements with the index when using it so we van pass the index
                ? {...item, favorite: false}
                : item
            )
        default:
            return state
    }
}
 

если вы посмотрите на параметр, который я передавал функции switch action.shortName , вместо action.type so она всегда возвращала значение по умолчанию для переключателя, которое является состоянием, как оно уже было.

Спасибо @SimoneCelli за то, что заставил меня задуматься о том, как я передавал и запускал отправку, и @Taxel за то, что сделал для меня очевидным, что я не использовал контекст для того, что на самом деле предназначено в основном: не нужно передавать свойства и функции сложным способом, включающим слишком много компонентов для этого.

Так что мой редуктор выглядит так, теперь он работает правильно:

 type Action = 
    | { type: 'favorite', shortName: string }
    | { type: 'unFavorite', shortName: string }

export function reducer(state: AppState, action: Action): AppState {
    switch(action.type){
        case 'favorite':
            console.log('favorite action triggered!!')
            return state.map( (item: Bot) => item.shortName === action.shortName // will have to map the elements with the index when using it so we van pass the index
                ? {...item, favorite: true}
                : item
            )
        case 'unFavorite':
            console.log('unFavorite action triggered!!')
            return state.map( (item: Bot) => item.shortName === action.shortName // will have to map the elements with the index when using it so we van pass the index
                ? {...item, favorite: false}
                : item
            )
        default:
            return state
    }
}
 

где я передаю правильный параметр в функцию переключения.