# #reactjs #redux #firebase-authentication #react-hooks #immer.js
Вопрос:
Я пытаюсь обновить учетные данные пользователя в проекте React/Redux с помощью машинописного текста. Я использую Firebase, поэтому учетные данные пользователя возвращаются в .then()
блоке signInWithPopup
метода.
Вот компонент. Он нарушен, потому что нарушает правила крючков. Я не могу найти обходной путь. Я думал, что использование useEffect
с именем объекта состояния currentUser
позволит мне запустить хук в корне функционального компонента после currentUser
обновления состояния local () внутри .then
блока.
Функциональный Компонент
import React, { useState, useEffect } from 'react';
import {
getAuth,
signInWithPopup,
GithubAuthProvider,
Auth,
} from 'firebase/auth';
import 'bulmaswatch/superhero/bulmaswatch.min.css';
import { storeState } from '../state';
import { UserData } from '../state/action-creators';
import { useTypedSelector } from '../hooks/use-typed-selector';
const UserAuth: React.FC = () => {
const [currentUser, setCurrentUser] = useState<UserData | null>(null);
const [loginButtonText, setLoginButtonText] = useState<string>('Log In');
const provider: GithubAuthProvider = new GithubAuthProvider();
const auth: Auth = getAuth();
useEffect(() => {
if (currentUser !== null) {
useTypedSelector(({ user }) => currentUser);
}
}, [currentUser]);
const signinHandler = () => {
signInWithPopup(auth, provider)
.then(({ user: { uid, email, displayName, photoURL } }) => {
console.log({ uid, email, displayName, photoURL });
const userPayload: UserData = {
uid: uid,
email: email as string,
displayName: displayName as string,
photoURL: photoURL as string,
};
setCurrentUser(userPayload);
console.log({ userPayload });
console.log({ storeState });
})
.catch((err) => {
const errorCode = err.code;
const errorMessage = err.message;
const email = err.email;
const credential = GithubAuthProvider.credentialFromError(err);
// todo - display errors
console.log(err);
console.log(errorCode, errorMessage, email, credential);
});
};
return (
<button className="gh-button button is-primary" onClick={signinHandler}>
<i className="fab fa-github" />
<strong style={{ marginLeft: '10px' }}>{loginButtonText}</strong>
</button>
);
};
export default UserAuth;
Это и есть useTypedSelector
крючок
import { useSelector, TypedUseSelectorHook } from 'react-redux';
import { RootState } from '../state';
export const useTypedSelector: TypedUseSelectorHook<RootState> = useSelector;
Это мой userReducer
для контекста
import produce from 'immer';
import { ActionType } from '../action-types';
import { Action } from '../actions';
interface UserState {
uid: string;
email: string;
displayName: string;
photoURL: string;
}
const initialState: UserState = {
uid: '',
email: '',
displayName: '',
photoURL: '',
};
const reducer = produce((state: UserState = initialState, action: Action) => {
switch (action.type) {
case ActionType.SET_USER:
const { uid, email, displayName, photoURL } = action.payload;
state.uid = uid;
state.email = email;
state.displayName = displayName;
state.photoURL = photoURL;
return state;
default:
return state;
}
});
export default reducer;
Примечания
Я использую Immer, чтобы легко изменять неизменяемое состояние
Если бы я мог просто подключить свой компонент к хранилищу Redux, чтобы я мог отправлять действие непосредственно из своего компонента — это тоже было бы здорово.
Я почти готов переключиться на контекстный API, потому что Redux нигде не так интуитивно понятен, как Vuex.
Ответ №1:
Это даже не проблема с исправлением. Как вы упомянули, это проблема с «правилами крючков». Все вызовы хука должны находиться на верхнем уровне компонента функции или другого пользовательского хука и никогда не могут быть вложены в условные операторы.
На самом деле из текущего кода неясно, что вы пытаетесь здесь сделать. Если мы проигнорируем аспект правил крючков, само использование селектора покажется неправильным:
useEffect(() => {
if (currentUser !== null) {
useTypedSelector(({ user }) => currentUser);
}
}, [currentUser]);
Даже если бы это было законное использование, оно не делает ничего полезного:
- Селектор игнорирует
user
поле, разрушаемое из состояния, и возвращаетcurrentUser
переменную, уже находящуюся в области действия useSelector
всегда возвращает значение, которое ваш селектор извлекает из объекта состояния Redux, но здесь возвращаемое значение полностью игнорируется
Я думаю, вы хотите сказать, что «когда я получу новый пользовательский объект от Firebase, я хочу обновить хранилище Redux для хранения этих пользовательских данных». В этом случае на самом деле вам нужно отправить действие, противоположное выбору данных из состояния.
Предполагая, что это так, то правильный подход здесь, вероятно, будет включать useState<UserData>
полное удаление и использование крючка React-Redux useDispatch
, позволяющего отправлять действия из компонента:
// The reducer file should export an action creator for this case
import { userLoaded } from './userSlice';
const UserAuth = () => {
const [loginButtonText, setLoginButtonText] = useState<string>('Log In');
// get access to the Redux store `dispatch` method
const dispatch = useAppDispatch();
const signinHandler = () => {
const provider: GithubAuthProvider = new GithubAuthProvider();
const auth: Auth = getAuth();
signInWithPopup(auth, provider)
.then(({ user: { uid, email, displayName, photoURL } }) => {
console.log({ uid, email, displayName, photoURL });
const userPayload: UserData = {
uid: uid,
email: email as string,
displayName: displayName as string,
photoURL: photoURL as string,
};
// Dispatch a Redux action containing this data
dispatch(userLoaded(userPayload))
})
.catch((err) => {
const errorCode = err.code;
const errorMessage = err.message;
const email = err.email;
const credential = GithubAuthProvider.credentialFromError(err);
// todo - display errors
console.log(err);
console.log(errorCode, errorMessage, email, credential);
});
};
return (
<button className="gh-button button is-primary" onClick={signinHandler}>
<i className="fab fa-github" />
<strong style={{ marginLeft: '10px' }}>{loginButtonText}</strong>
</button>
);
};
Я думаю, вам не хватает некоторых ключевых моментов понимания того, как работает Redux и как его правильно использовать. Я настоятельно рекомендую прочитать учебник «Основы Redux» в наших основных документах Redux, в котором рассказывается «как правильно использовать Redux».
Еще пара замечаний по коду:
- Вы не должны создавать «экземпляры», такие как поставщик аутентификации Github, внутри логики рендеринга. Сделайте это в эффекте или просто непосредственно в обработчике щелчка
- Вы также должны использовать наш официальный пакет инструментария Redux для написания своей логики Redux (редукторы и т. Д.). В RTK уже встроен Immer, и он разработан для хорошего опыта использования TS. В частности,
createSlice
API автоматически создаст создателей действий и типы действий для вас на основе определяемых вами редукторов. - Обычно рекомендуется не использовать этот
React.FC
тип. Вместо этого просто объявите типprops
в компоненте, если таковые имеются, и пусть TS выведет тип возвращаемого значения.
Комментарии:
1. Спасибо вам за ответ. Это хорошо продумано. Я разочарован документами Redux. Я бился головой о стену. Я люблю Vuex, мне комфортно с контекстным API, я начинаю ненавидеть Redux. Я взламывал этот беспорядок в течение нескольких дней. Государство работает, за исключением этой аутентификации. Я могу изменить состояние, отправив его в действия состояния или файлы состояния, но тогда отправка не работает за пределами хранилища. Предполагалось
useEffect
, что это будет обходной путь для обновления. Я добавил {user} только для того, чтобы типизированный селектор знал, какое состояние я пытаюсь обновить.2. Можете ли вы уточнить, в чем конкретно вы «разочарованы» нашими документами? Я также написал наши руководства по редактированию документов, и они должны быть очень всеобъемлющими в объяснении того, как использовать инструменты редактирования и редактирования. FWIW, если у вас есть вопросы, я и другие сопровождающие Redux тусуемся на
#redux
канале в дискорде Reactiflux . Глядя на код, который вы показали, похоже, что вам не хватает какого-то фундаментального понимания того, как работает диспетчеризация, но я не уверен, в чем проблема. Что вы подразумеваете под «отправка не работает за пределами магазина»?3. Я буду честен… Я очень смущен тем, какие ресурсы Redux вы просматривали. Мы ничего не упоминаем о «плагинах» в наших документах. И да, изучение как React, так и Redux включает в себя изучение конкретных новых терминов и понятий. В конечном счете, реальная проблема в вашем исходном коде заключается в том, что вы не отправляли действие . Вот как происходят все обновления состояния Redux:
store.dispatch({type: 'something/happened"})
. Вы пытались написать какой-то код «состояния чтения» в том месте, где вам нужно было написать код «запуска обновления».4. Если вы читаете наши учебные пособия, они научат вас всему, что вам нужно знать, чтобы использовать Redux, либо написав его «вручную» без каких-либо абстракций, либо с помощью нашего (рекомендуемого) пакета инструментов Redux для написания логики. Я хотел бы, чтобы заглянуть в этот
#redux
канал в Reactiflux так мы можем поговорить дальше, даже если вы переключились на что-то еще — я подозреваю, что вам просто не хватает одного или двух основных частей понимания здесь, и я хотел бы попытаться определить, что конкретный вопрос и помочь понять, как это должно работать.5. Для «Redux на против контексте» что конкретно, пожалуйста, см. Мои объяснения , почему реагируем условиях не является «Государственное управление» инструмент (а почему бы его не заменить Redux на) , реагировать, Перевождь, и контекст поведения , и когда (и когда нет), чтобы достигнуть «возвращение» , которые охватывают как это разные инструменты, которые решают разные проблемы.