Олег
6 февр. 2021 г., 16:46

Вопрос про console.log в функциях и хуках

Вопрос в том, что не совсем понимаю, как правильно использовать console.log в функциях, чтобы понимать, что получаю в результате.

Сейчас делаю учебный проект мне нужно забрать с базы данных объект в котором карточки с покемонами и вывести их на странице.

Для этого использую такой код:
import { useState, useEffect } from 'react'; import Layout from '../../components/Layout'; import PokemonCard from '../../components/PokemonCard'; import datbase from '../../service/firebase' import s from './style.module.css'; const GamePage = () => { const [pokemons, setPokemons] = useState({}); console.log('####: usePokemons', pokemons); useEffect(() => { datbase.ref('pokemons').once('value', (snapshot) =>{ console.log('####: snapshot', snapshot.val()); setPokemons(snapshot.val()); }) }, []); const handleClickCard = (id) => { setPokemons(prevState => { return Array.from(prevState, (pokemons) => { if (pokemons.id === id) { pokemons.isActive = !pokemons.isActive; } return pokemons; }); }) }; console.log('####: SetPokemons', pokemons); return ( <> <Layout id={2} title='Layout 2 title' descr='description' colorBg='#777' > <div className={s.flex}> { Object.entries(pokemons).map(([key, {name, img, id, type, values, isActive}]) => <PokemonCard onCardClick={handleClickCard} isActive={isActive} key={key} id={id} name={name} img={img} type={type} values={values} />)} </div> </Layout> </> ); }; export default GamePage;
Примерно понял из уроков, что если после const поставить console.log('####: usePokemons', pokemons); для примера для хука useState он показывает, что передаю пустой объект, анологично для хука useEffect если поставить console.log('####: snapshot', snapshot.val()); то он показывает, что мы туда получаем. А вот с третьей функцией handleClickCard, которая отвечает за отоброжение карточек и преоброзования их в масив проблема, не понимаю, куда ставить console.log для проверки, чтобы понять, что она возвращает. Сейчас поставил ее после самой функции, но не уверен, что это правильное решение. Хотя вроде видно в консоли, что он отображает этот console.log и отображает объект в нем, но я как-то совсем не уверен, что он именно из последней функции его отображает. Да и по идеи он должен был его преоброзовать в массив, а он возвращает объект.


Олег, во-первых, хорошо бы выложить проект на гитхаб (тем более, что он учебный), чтобы проще было разбираться.

Кстати, если добавить здесь именно проект (пока правда только через старый сайт https://old.prisma-cms.com/projects/create ). Так вот, если добавить проект, в нем можно указать ссылку на репозиторий, и в нем же создавать задачки с просьбой о помощи, так же расписав что и как. Такой формат более удачный. Как мне видится.

Но это так, к слову.

По теме: ты совсем запутался и неправильно воспринимаешь происходящее, и неправильно называешь явления.

> Примерно понял из уроков, что если после const поставить console.log('####: usePokemons', pokemons); для примера для хука useState он показывает, что передаю пустой объект

После const ты вообще не можешь поставить console.log (а правильней говорить не ставить, а писать). В данном случае пишешь не после const, а на следующей строке, и не за const, а за const [pokemons, setPokemons] = useState({}); И чтобы понимать что происходи дальше и вообще, тебе надо разобраться что происходит именно здесь, то есть в const [pokemons, setPokemons] = useState({});. И важно здесь понимать синхронное выполнение кода происходит или асинхронное? Потому что от этого зависит будет ли заблокировано дальнейшее выполнение кода пока не выполнится useState({}) или не будет.

Читай очень внимательно про синхронный и асинхронный javascript: https://developer.mozilla.org/ru/docs/Learn/JavaScript/Asynchronous/Introducing (включая вложенные ссылки).

Так вот, важно уяснить, что по умолчанию в JS все функции синхронные, то есть выполнение происходит последовательно, то есть результат выполнения функции возвращается только тогда, когда функция выполнится. Но тут есть подводный камень в том плане, что в качестве результата выполнения функции может быть возвращен, к примеру, объект Promise. То есть это хоть и результат выполнения данной функции, но реальный результат будет возвращен позже, к примеру, через вызов .then(...), или, если мы находимся внутри функции, объявленной с ключевым словом async, используем await.

Да, это все очень сложно, и материала не мало, но это важно выучить и понять. Про асинхронность спрашивают на любом собеседовании, и без этого знания, думаю, даже в джуны нигде не возьмут, потому что иначе не сможешь программировать вообще, вот прям как в этом примере не понимаешь, как что и когда выполняется.

Так вот, запомни: setState (в том числе через хук useState, равно как и другие хуки) выполняется асинхронно. Чтобы понять, вот такой пример посмотрим:


Вот смотри, здесь console.log выполняется не только за выполнением useState, но и за двумя блоками условия if.
При чем очевидно, что первое условие if(count === 0) выполнится обязательно при первом же рендеринге компонента, ведь мы в useState указали начальное значение - 0.
По логике, раз условие выполнилось и вызвался setCount(1) еще до выполнения console.log, у нас count уже должен стать 1 и в console.log должно вывестись 1. Но это не так. На самом деле у нас выведется и 0, и 1, как будто console.log вызвалось два раза. Более того, console.log не как будто два раза вызовется, а реально два раза. Почему так? Потому что при каждом изменении входящих свойств props или изменении текущего состояния state, у нас выполняется ререндеринг компонента, то есть вся его логика render выполняется опять от начала и до конца. И что же у нас на самом деле здесь произошло?

1. У нас установилось стейт-значение count = 0
const [count, setCount] = React.useState(0);


2. У нас выполнилось условие сравнения значения и вызвался метод изменения состояни.
if(count === 0) { setCount(1); }


И вот здесь очень важно понимать, что мы только вызвали изменение состояни, но состояние у нас еще не поменялось. Более того, выполнение кода здесь не прервалось, так что мы идем дальше.

3. Второе условие у нас не выполнилось и вызов изменения состояния не произошел.
if(count === 3) { setCount(4); }
4. Мы дошли до вывода переменной count в консоль.
console.log('count', count);
И вот здесь важно еще один момент учесть - замыкания. Дело не только в том, что у нас setState асинхронная, но и в том, что переменную count мы получили выше выполнения логики if-setState. При этом эта переменная со своим собственным значением, а не ссылка на какой-нибудь сторонний объект, так что с момента ее инициализации, ее значение не поменяется в течение всей ее жизни. А у нас она инициировалась со значением 0. Вот мы в консоль и получаем 0.

5. Выполнился окончательный рендеринг HTML-разметки.

6. Мы ведь помним, что у нас выполнилось первое условие count === 0 и вызов изменения состояния? Вот. Состояние изменилось и происходит ререндеринг, то есть повторное выполнение всей логики компонента. И вот там у нас уже count объявляется с новым значением - 1. И пошли по кругу от пункта 1.

Второй пункт у нас не выполняется. Третий тоже. К 4 пункту мы пришли без изменений со значением count = 1 и получаем в консоль вывод 1. Опять пункт 6, то есть рендеринг HTML.

В данном случае мы сразу получили 2 рендеринга компонента, потому что сразу же по условию изменили его состояние. Теперь, чтобы счеткик пошел дальше, нам уже надо кликать кнопку, чтобы вызвать обновление стейта. Кликаем раз и получаем новый count = 2 в стейте. Опять проходим 6 этапов и опять у нас условия не выполняются, так что в консоль как есть выводится 2. Кликаем еще раз. Опять все 6 пунктов, только в п 3 у нас срабатывает условие count === 3. Как и в первом рендеринге, у нас выполнение не прерывается и в консоль выводится 3, но у нас изменился стейт и выполняется новый рендеринг с числом 4. На выходе мы получили в консоль все пять выводов 0 1 2 3 4 5.




Покликать можно здесь: https://codepen.io/fi1osof/pen/RwoapyR

Надеюсь ты освоишь данный материал и тебе станет понятней.
Очень детально расписал, теперь более понятно, но не все конечно буду изучать синхронный и асинхронный javascript.

Теперь на примере моего кода, попробую воспроизвести, что понял.
import { useState, useEffect } from 'react'; import Layout from '../../components/Layout'; import PokemonCard from '../../components/PokemonCard'; import datbase from '../../service/firebase' import s from './style.module.css'; const GamePage = () => { const [pokemons, setPokemons] = useState({}); console.log('####: useStatePokemons', pokemons); useEffect(() => { datbase.ref('pokemons').once('value', (snapshot) =>{ setPokemons(snapshot.val()); console.log('####: useEffectPokemon', snapshot.val()); }) }, []); const handleClickCard = (id) => { setPokemons(prevState => { return Object.entries(prevState).reduce((acc, item) => { const pokemon = {...item[1]}; if (pokemon.id === id) { pokemon.isActive = true; }; acc[item[0]] = pokemon; return acc; }, {}); }); }; return ( <> <Layout id={2} title='Layout 2 title' descr='description' colorBg='#777' > <div className={s.flex}> { Object.entries(pokemons).map(([key, {name, img, id, type, values, isActive}]) => <PokemonCard onCardClick={handleClickCard} isActive={!isActive} key={key} id={id} name={name} img={img} type={type} values={values} />)} </div> </Layout> </> ); }; export default GamePage;

Для того чтобы получить первоначальное состояние useState({}); написал console.log('####: useStatePokemons', pokemons); после получения состояния.

Для того чтобы получить snapshot в аргументе setPokemons написал console.log('####: useEffectPokemon', snapshot.val()); после изменения состояния.

В итоге получил такой вывод в консоли (картинка внизу):

1. На первом рендеринге он отбразил текущее состояние useState пустой объект, который я туда положил.
2. При первом же рендеринге, после текущего состояния, он так же увидел измененное состояние, которое пришло через setState.
3. После того, как DOM обновился он уже выполнил useEffect и сделал новый рендеринг.

Правильно понял или все-таки не совсем?

И что мне все таки делать с этой функцей? Как получить состояние карточки при клике в console.log я его уже куда только не засовывал он нигде не работает .


const handleClickCard = (id) => { setPokemons(prevState => { return Object.entries(prevState).reduce((acc, item) => { const pokemon = {...item[1]}; if (pokemon.id === id) { pokemon.isActive = true; }; acc[item[0]] = pokemon; return acc; }, {}); }); };


> Для того чтобы получить первоначальное состояние useState({});

Уходи от такой формулировки. Это неправильная формулировка. Правильная такая: Для того чтобы получить состояние useState({});

Никогда не думай первоначальное это состояние или нет. Всегда думай - это текущее состояние. Не думай, что useState что-то устанавливает. Она, конечно, устанавливает, но только при первом вызове. То есть в последующие она не будет ничего устанавливать, а всегда (как и в первый раз) возвращать массив с двумя элементами. 1 - состояние. 2 - метод для установки нового состояния. Ты, конечно, можешь смотреть на то, что передал в useState, чтобы понимать, что туда будет установлено по умолчанию, но всегда должен думать только обо дном - что ты в итоге получишь из состояния. Так правильно думать в случае с реактом.

> Для того чтобы получить snapshot в аргументе setPokemons написал console.log('####: useEffectPokemon', snapshot.val()); после изменения состояния.

Опять неправильно. Вообще все неправильно.

Нельзя получить что-то в аргументе. Можно только передать в качестве аргумента, и можно получить из аргумента. А получить в аргументе - это бессмыслица. Получить можно в функции. И вот теперь разберись что у тебя в аргументах, кто передал эти аргументы и что ты делаешь с полученными аргументами.

А теперь самое главное: а кто тебе сказал, что handleClickCard обязательно вызывается? Ты смотрел исходный код PokemonCard? Там вообще ожидается свойство onCardClick? Оно там вообще обрабатывается? Или по какой такой логике ты считаешь, что любое передаваемое свойство обязательно доложно как-то обрабатывается?

Еще раз: прикладывай ссылку на проект. Кусок кода, вырванный из контекста - то же самое, что и предложение, вырванное из контекста.

И, посмотрев код твоего проекта учебного, могу сказать следующее: учебный проект - это конечно же хорошо. На нем интересней учиться. Но учишься ты совсем не тому, чему следует.

1. Create React App - это вот прям только для обучения. Боевые проекты на нем не следует делать. Если ты взял проект со стороны, то ОК. Если ты делаешь сам с нуля и взял его вместо того, чтобы взять https://github.com/prisma-cms/nextjs, то совсем не ОК.

2.
import s from './style.module.css'; import cn from 'classnames' <div className={cn(s.pokemonCard, { [s.active]: isActive })}>
Забудь, что ты когда-то это видел. Не используй никогда и нигде. Я видел уже много людей, которые думали, что так норм. А потом открывали для себя styled-components и прозревали. Не учись тому, что не нужно и чему есть более правильные альтернативы.

3. Переставая писать в .js, пиши в .tsx
Если бы ты использовал TypeScript, то он бы тебе подсказал, что ты передаешь то, чего от тебя не ждут вообще, и сэкономил бы много времени.

Еще раз: не учи React + JavaScript, учи сразу React + TypeScript. Вопрос в нескольких днях. Но ты сразу встанешь на более правильный путь.

Вот тебе домашнее задание: Возьми теперь https://github.com/prisma-cms/nextjs и перенеси туда свой проект, сразу на TS. Можешь прям создать здесь проект и там вопросы задавай и задачи ставь. Я ближайшие часа три здесь еще буду, так что сразу смогу сопроводить. Вот перенесешь туда и сразу ощутишь разницу. И начнешь учиться в правильном ключе.

Олег, ты давай не затягивай с переходом на новый движок. Я хочу кое-какие полезные наработки выкатить (хуки), но пока ты не понимаешь TS и прочие базовые вещи, это не имеет смысла.
Я не затягиваю мне нужно его доделать там немного осталось, чтобы переносить готовое решение. Плюс ты мне мозг взорвал асинхроностью, вчера весь день изучал асинхроность, промисы, замыкание, контекст и.тд., но пока так и непонял, как работает твой пример. Нашел вот такой сервис, который демнострирует, как работает event loop http://latentflip.com/loupe/ , примерно понятно, но твой пример не получается там посмотреть, там редактор немного по другому работает, чем на codepen.io в return ругается на кнопку ее нужно в html выносить, а как ее вернуть там не понимаю. Если бы ты смог немного переписать свой пример под этот сервис было бы очень круто, потому-что сейчас сцуко не получается понять, как все-таки работает твой пример.


Вот сейчас появилась идея обвешать весь твой код console.log, может будет более понятно, сейчас попробую.
>> Если бы ты смог немного переписать свой пример под этот сервис

Нет, не буду. Это получится медвежья услуга. Ведь нельзя просто так переписать с сохранением функционала реакта. Мы ведь в реакте пишем мало, а под капотом происходит много. Поэтому мне придется, во-первых, написать больше кода (а тогда и материала для изучения у тебя возникнет больше, и сложнее будет освоить). Во-вторых, ты не сможешь мыслить в стиле реакта. Тебе надо осваивать именно в реакте, чтобы мыслить в реакте, потому что там другая парадигма. Так что осваивай то, что есть.

А вообще, тебе наверно поможет лучшее освоение классовых компонентов, потому что очевидно, что ты не понимаешь, что есть объект стейта (в классах он через this.state), и что при выполнении setState() происходит обновление этого объекта. И что такое поведение функций называется "Побочный эффект", когда функция вызывается не для того, чтобы что-то вернуть, а чтобы что-то изменить.
Вообщем попытаюсь прям по микрочастям все раложить, чтобы разобраться.


Олег, неправильно делаешь (просто картинку прислал). Делай клон кодепен-проекта и присылай ссылку.
Кодепен не выводит их одновременно. Это же две разных строчки, то есть два отдельных вывода. Просто пауза между ними минимальная, так как в ответ на изменение стейта происходит сразу ререндеринг, и это срабатывает без твоего участия, то есть не приходится кликать кнопку для этого.

У тебя там сейчас отработало все так, как и должно было отработать. Если ты не понимаешь почему так происходит, а главное - пытаешься заставить код выполнять так, как ты этого хочешь, то займись корректировкой своего мышления. Ошибка многих начинающих в том, что они пытаются научиться магии, думая, что программировать - это заставлять машину делать то, что мы хотим. Это неправильно. Научиться программировать - это научиться понимать машину и понимать что произойдет, если сдеть то или иное, и главное - научиться понимать машину, почему происходит так, как происходит, а не так, как мы этого ожидали (то есть научиться фиксить баги в себе, а не в коде).

Сейчас твоя проблема все еще в том, что ты неправильно понимаешь выполнение useState. Пойми: useState всегда именно возвращает, а не устанавливает значение. Ты думаешь так: в первый раз она установит и потом вернет значение из стейта, а потом она будет сразу возвращать из стейта. Это неправильное восприятие. Хотя конечно же по сути именно так и происходит, но все же правильно понимать так: она сразу же вернет или значение из стейта (если оно есть), или значение, переданное по умолчанию (и присвоит это значение стейту, но так, что это не вызывает ререндеринга).

Для того, чтобы это понять, посмотри вот этот код:
var state = undefined; function useState(defaultValue) { if(state === undefined) { state = {}; state.field = defaultValue; } return state.field; } function setState(value) { setTimeout(() => { state.field = value; }, 1000) } console.log("state", state); var count = useState(5); console.log("count 1", count); console.log("state 1", state); var count = useState(7); console.log("count 2", count); console.log("state 2", state); setState(10); console.log("count 3", count); console.log("state 3", state); setTimeout(() => { console.log("count 4", count); count = useState(); console.log("count 4", count); console.log("state 4", state); }, 2000);
Выполни его в консоли браузера и попробуй оценить результат. А результат будет таков:

state undefined count 1 5 state 1 {field: 5} count 2 5 state 2 {field: 5} count 3 5 state 3 {field: 5} # здесь id таймера из setTimeout 109 # Этот результат появится через 2 секунды. count 4 5 count 4 10 state 4 {field: 10}
Обравти внимание, что state = undefined только в первый раз. Затем у нас всегда один и тот же объект стейта, только меняется его свойство.

В setState я прописал вызов через setTimeout, которое и позволяет симитировать работу нативного setState в том плане, что мы не получаем сразу измененного объекта стейта, то есть код далее выполняется все еще со старым значением стейта.
А второй setTimeout с двухсекундной задержкой имитирует повторный ререндеринг, в котором уже новое значение стейта, то есть хотя мы и не вызываем в этом блоке setState, а получаем актуальное значение через useState, мы получаем именно новое значение. При этом я специально здесь сразу два вывода count сделал, чтобы показать тебе, что старая переменная count даже через 2 секунды содержала именно старое значение, после чего мы ей присвоили новое значение.

Вот научись понимать эту логику и то, что код рендерера каждый раз выполняется от и до.
Учимся понимать машину и асинхронно взрываем себе мозг :)

Итак, в бар входит в ковбой и требует выпивку - это объявление, декларация намерений.
Бармен спрашивает - чем будешь платить: деньгами, отдашь пистолет или отработаешь мытьем посуды? - Я заплачу, - отвечает ковбой, - у меня есть деньги. - Это определение типа данных.
Тогда плати 5 баксов и получай выпивку - Ковбой платит и веселье начинается - Это инициализация, присвоение начального значения, запуск процесса в работу.

Шаг 1.
var state = undefined;
Инициализируем переменую state со значением undefined. Машина сохраняет ее в памяти со значением undefined.

Шаг 2.
function useState(defaultValue) { if(state === undefined) { state = {}; state.field = defaultValue; } return state.field; }
Инициализируем функцию useState в аргуаментах ждем парметр defaultValue с условием, если state === undefined присвой state пустой обьект в который положи ключ field со значением defaultValue. Машина видит, что мы инициализировали функцию useState со значением state = undefined.

Шаг 3.
function setState(value) { setTimeout(() => { state.field = value; }, 20000) }
Инициализируем функцию setState в аргуаментах ждем парметр value в теле функции вызываем функцию setTimeout, которой в объект state записываем значение ключ field со значением value. Данная функция отправляется в стек, но так как внутри функции setState, находится функция setTimeout, которая не является частью JavaScript движка, а является Web API браузера как дополнительный функционал она отправляется в Web API. Далее Web API браузера запускает таймер в 2000ms, оставляя на фоне setTimeout().

Шаг 4.
console.log("state", state);
Следующая строка в нашем скрипте это console.log("state", state); , отправленное в стек и выкинутое оттуда после выполнения. Это получается наш первый выкинутый в консоль результат в данный момент наш state = undefined, поэтому получаем:

1. "state" undefined

Шаг 5.
var count = useState(5);
Инициализируем переменую count и делаем вызов функции useState(со значением 5) и присваеваем результат перемной count. В аргументе defaultValue функция useState теперь хранится значение 5. Теперь в функции у нас идет проверка условий if(state === undefined), так как state у нас по прежнему undefined условие true, поэтому оно выполняется дальше создаем пустой объект state = {};, переходим к следуещему условию создаем в объекте ключ field со значением defaultValue получается field: 5, так как мы вызвали функцию useState и положили в defaultValue = 5. Теперь return нам возвращает Return value 5.

Здесь тоже мне не свосем понятно, когда функция useState попадает в стек при вызове, как сейчас или при инициализации она сразу попадает в стек и жет своего выполнения?

Шаг 6.
console.log("count 1", count);
Следующая строка в нашем скрипте это console.log("count 1", count); , отправляется в стек и выкидывается оттуда после выполнения. В результате мы получаем наш второй вывод в консоль.

2. "count 1" 5

На этом месте все, я уже ничего не понимаю на самом деле, что происходит. Во-первых где наша функция setState со своим setTimeout она здесь вообще в процесе участвует или мы про нее вообще забыли?

Откуда при инициализации функции setState появился count со значением undefined (откуда он вообще count этот берется)?

В этом шаге мы получаем в консоль лог count из инициализированной переменой var count = useState(5); или он к нам все-таки прилетает из функции setState?


Сейчас боле менее складывается понимание (не буду придераться к используемым терминам, это не особо важно). Но ты слишком все усложняешь. Зачем тебе думать является ли setTimeout частью движка JavaScript или это часть Web API? Зачем тебе думать что попадает в стек, а что нет? Конечно же это хорошо, когда ты понимаешь, но в большинстве случаев это не надо вообще. Даже я не уверен, что понимаю что такое стек, и что вообще об этом когда-нибудь задумываюсь. До того, как начал изучать Си, я вообще о нем не думал.

Надо сильно упрощать все до уровня Есть или Нету. setTimeout отработал? Отработал. Значит он есть. useState отработала? Отработала. Значит она есть. Что тебе еще надо? Зачем тебе вот это чего частью оно является или нет? Не усложняй.

>> Здесь тоже мне не свосем понятно, когда функция useState попадает в стек при вызове, как сейчас или при инициализации она сразу попадает в стек и жет своего выполнения?

А что здесь не ясно? Ты же сам пишешь: "Инициализируем функцию setState". Вот как только инициировали, все, она есть. Доказательство тому - успешный вывод в любом месте за ее объявлением. К слову, Инициализация в данном случае - неправильное употребление. Инициализация - это объявление переменной сразу с задаваемым значением. А в данном случае у нас объявление функции. Функция сама по себе значения не имеет. И если переменную объявлять, но не задавать ей сразу значение, а присваивать значение позднее (пусть даже на следующей строчке), то это отдельно объявление, и позже присвоение. Здесь уже инициализации не будет. Начитался я тут всякого в книге по Си. Вот там это важно, там разное поведение в момент компиляции программы. А здесь нам это не нужно. Но на всякий случай уточнил.

Так вот, про объявление функции: еще раз: когда объявили, тогда она и есть. Но объявить - это не вызвать ее. То есть она не работает, ничего не читает и никаких значений не инициирует, пока ее не вызовут. Даже указав в аргументе значение по умолчанию, это значение будет или не будет использовано тогда, когда функция будет вызвана, а не когда функция объявлялась. И так будет каждый раз при вызове, в зависимости от того, какие параметры в нее передавались. При этом, когда мы вызываем функцию, у нас функция не висит, а выполняется, и возвращается ее результат. Поэтому формулировка "где наша функция setState со своим setTimeout...?" вообще не правильная. Наша функция ровно там, где она и была - где мы ее объявили. И далее там, где она видна, то есть находится она в области видимости или нет. Опять-таки, в нашем примере практически все в одной области видимости (кроме того, что выполняется внутри функций). И обе наши функции объявлены в той же области видимости, что и все остальное происходит. Иначе бы у нас попытка обратиться к функции, недоступной в нашей области видимости, просто приводила бы к ошибке. Но у нас же не возникает ошибок, верно?

>> Откуда при инициализации функции setState появился count со значением undefined (откуда он вообще count этот берется)

А где в моем примере вообще связь с setState и count? И где ты вообще увидил, чтобы хоть в одном месте у меня count был undefined? Но если ты вдруг решил поиграться и в setState в тело функции попытаться обратиться к count и получил undefined, то совсем не удивительно. Ведь count инициируется ниже по коду. var count = useState(5);
Что здесь не ясно?

А useState сама по себе не возвращает значение count. Она возвращает значение state.field || defaultValue. Где ты в теле этой функции видишь count? Ткни пальцем.

Добавить комментарий