Не тригереться reducer (useReducer + Context) в Typescript

💡 Усі статті, обговорення, новини про Front-end — в одному місці. Приєднуйтесь до Front-end спільноти!

Роблю невеличкий пет-проект, ніколи не тикав Typesctipt та все роблю імпровізуючи, в офіційних доках react прочитав топік про scaling Reducer and Context та намагаюся теж саме зробити у себе в проекті. Але чомусь не тригиреться reducer. Дуже багато спілкувався за чатоп гпт (який постійно казав, що начебто все написано правильно і проблем він не бачить) та на стек оверфлоу, ніхто не зміг допомогти.

Це мій файл поєднання context & reducer

const BookContext = createContext<{ books: Book[] }>({ books: initialBooks });
const BookDispatcherContext = createContext<Dispatch<Actions>>(() => {});


interface Props {
 children: ReactNode
}


type Actions =
 | { type: "added"; payload: Book }
 | { type: "deleted"; payload: number };


type State = {
 books: Book[];
};


export default function BooksProvider(props: Props) {
    
 const initialBooksState = { books: initialBooks };
 const [booksState, dispatcher] = useReducer(reducer, initialBooksState);
 const { children } = props;


    return (
  <BookContext.Provider value={{ books: booksState.books }}>
   <BookDispatcherContext.Provider value={dispatcher}>
    { children }
   </BookDispatcherContext.Provider>
  </BookContext.Provider>
 );
}


export function useBooks() {
  return useContext(BookContext).books;
}


export function useDispatcher() {
  return useContext(BookDispatcherContext);
}



function reducer(state: State, action: Actions) {
  switch (action.type) {
  case "added":
   return { ...state, books: [...state.books, action.payload] };
  case "deleted":
   return { ...state, books: state.books.filter(book => book.id !== action.payload) };
  default:
   return state;
  }
} 
Ось головний файл, де передається dispatcher & context
// App.tsx
export default function App() {
 return (
  <div className="container">
   <ChakraProvider>
    <BooksProvider>
     <Home />
     <Finished />
    </BooksProvider>
   </ChakraProvider>
  </div>
 );
}
Файли Home.tsx та Finished.tsx використовують BookCardChakra.tsx для того, в map’і виводити карточки з книгами, код виклику диспетчер функції теж знаходиться тут.
export default function BookCardChakra({ book }: { book: Book }) {
const dispatcher = useDispatcher();


const handleDeleteButton = (bookId: number) => {
console.log('clicked on id ' + book.id); // тут виводить коректний id книги
console.log(dispatcher); // постійно виводить () => {}
dispatcher({type: 'deleted', payload: bookId}); // нічого не відбувається
}


return (
  ...
 <Button variant="ghost" colorScheme="blue" onClick={() =>   handleDeleteButton(book.id)}> Delete</Button>
 </ButtonGroup>
 ...
 );
}
Також додавав звичайний console.log в reducer функцію, але він теж не виводиться, ви моя остання надія.
👍ПодобаєтьсяСподобалось0
До обраногоВ обраному0
LinkedIn
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Дозволені теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Відчуття, що десь переплутана структура компонентів, тому хук витягує initialState, а не той який мав би давати провайдер. Перевірте, щоб компоненти в яких ви витягаєте dispatcher були точно обгорнуті у провайдер

тут структура наступна — App.tsx через BooksProvider охоплбє Home та Finished, ці два компонента за допомогою map’и проходять по контексту наступним чином
{booksContext
.filter((book: Book) => !book.finished)
.map((book: Book) => (

))}
і вже (як я думаю повинно бути) контекст диспетчеру передається в BookCardChakra, де вже я його використовую

Для того щоб вам могли швидше допомогти — я б радив би перенести приклад на StackBlitz і дати посилання. Розбиратися зі StackBlitz набагато, набагато простіше

Він використовує дефолтний діспатчер який ви передели йому при створенні, зараз подивлюсь чому

саме так, тому я і не можу зрозуміти чому саме дефолтний використовується

Тому що ви поставили його в комопонент який ніколи не використовується — в App.tsx

А використовуєте роутер — який рендерить або Home або Finished

Для того щоб виправити помилку:

root.render(
  <React.StrictMode>
    <BooksProvider>
      <RouterProvider router={router} />
    </BooksProvider>
  </React.StrictMode>
);

омг, бачили б ви моє лице...дуже вдячний!

Буває. Не забудьте погладити кота, якщо він у вас є 🐈

Воно наче так, але є питання:

— Слід переконатися, що reducer та initialBooksState визначені коректно, і useReducer не видає помилок. В тому коді що Ви навели наче виглядає добре.
— Те ж саме з useBooks.
— Перевірте правильність передачі book.id в handleDeleteButton:
У функції handleDeleteButton Ви використовуєте book.id, але в аргументах передаєте bookId. Переконайтеся, що передаєте правильний bookId.
— Перевірте правильність виклику dispatcher:
У функції handleDeleteButton Ви викликаєте dispatcher({type: ’deleted’, payload: bookId}). Переконайтеся, що dispatcher функціонує коректно, і Actions має правильну структуру. Те ж саме зауваження до bookId(чи book.id)

дякую за відповідь, але так, все це я вже перевіряв декілька разів, reducer написаний коректно, id передається теж правильний (відповідно до тієї книги, на яку я натиснув), головна проблема — дефолтне значення диспетчеру в контексті ( ()=>{}) не замінується на те, яке я передав у провайдері

По-перше, саме через такі патерни хочеться свічнутись з фронтенда кудись :)
По-друге, в документації у createContext передається null. «Here, you’re passing null as the default value to both contexts. The actual values will be provided by the TaskApp component.» Може через те що у вас там порожня функція замість null, реакт її не переасайнює на реальний діспатчер? Бо получається що в івент хендлері у вас саме ця () => {} і є діспатчером, тому очікувано, що стейт не міняється після виклику цієї функції.

по-перше я тільки вчусь, але дякую за зауваження, по друге проблема в тому, що документація для js, а не ts, навіть, якщо я напишу щось типу такого const BookDispatcherContext = createContext | null>(null); — це й досі не працює

Я ж нічого :) розумію, що якщо такий патерн є в доках, треба опанувати для загального розвитку. Але це жесть якась

Підписатись на коментарі