#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
}
}
где я передаю правильный параметр в функцию переключения.