#reactjs #react-redux #state #mapstatetoprops
#reactjs #реагировать-redux #состояние #mapstatetoprops
Вопрос:
Я пытаюсь впервые вставить компонент формы в свое приложение, используя Redux, и пытаюсь разобраться, для чего мне нужно создавать редукторы / действия.
В других компонентах мой пользователь и сообщения передаются в mapStateToProps, и они работают корректно. Однако в этом компоненте я извлекаю данные из своего бэкэнда для полей таблицы в методе componentDidMount, и я не уверен, сохраняются ли в Redux только данные, которые должны быть изменены.
Нужно ли мне также создавать редуктор для формы? или он отправляется прямо в серверную часть / node / postgresql. Я намереваюсь создать таблицу, которая обновляется всеми самыми последними данными, чтобы я мог видеть логику ее автоматического добавления к полученным данным.
Я довольно новичок в React / JavaScript, поэтому моя логика может немного отличаться, поэтому любые советы будут оценены.
diveLogForm.component.js
export class DiveLogForm extends Component {
constructor(props){
super(props);
this.handleSubmitDive = this.handleSubmitDive.bind(this);
this.onChangeDiveType = this.onChangeDiveType.bind(this);
this.onChangeSchoolName = this.onChangeSchoolName.bind(this);
this.onChangeCurrent = this.onChangeCurrent.bind(this);
this.onChangeVisibility = this.onChangeVisibility.bind(this);
this.onChangeDiveDate = this.onChangeDiveDate.bind(this);
this.onChangeMaxDepth = this.onChangeMaxDepth.bind(this);
this.onChangeDiverUserNumber = this.onChangeDiverUserNumber.bind(this);
this.onChangeVerifiedBySchool = this.onChangeVerifiedBySchool.bind(this);
this.onChangeDiveNotes = this.onChangeDiveNotes.bind(this);
this.onChangeDivePoint = this.onChangeDivePoint.bind(this);
this.state = {
diveTypeID: "",
diveSchoolNameID: "",
diveCurrentID: "",
diveVisibilityID: "",
diveDate: "",
diveMaxDepth: "",
diverUserNumber: "",
diveVerifiedBySchool: "",
diveNotes: "",
divePoint: "",
currentList: [],
regionList: [],
diveTypeList: [],
visibilityList: [],
diveSpotList: [],
currentUser: [],
loading: false,
};
}
componentDidMount() {
pullCurrentFields().then((response) => {
const { data } = response;
this.setState({ currentList: data.data });
});
pullRegionFields().then((response) => {
const { data } = response;
this.setState({ regionList: data.data });
});
pullDiveTypeFields().then((response) => {
const { data } = response;
this.setState({ diveTypeList: data.data });
});
pullVisibilityFields().then((response) => {
const { data } = response;
this.setState({ visibilityList: data.data });
});
pullDiveSpotFields().then((response) => {
const { data } = response;
this.setState({ diveSpotList: data.data });
});
//this.props.userDiveLogList();
}
onChangeDiveType(e) {
this.setState({
diveTypeID: e.target.value,
});
}
onChangeSchoolName(e) {
this.setState({
diveSchoolNameID: e.target.value,
});
}
onChangeCurrent(e) {
this.setState({
diveCurrentID: e.target.value,
});
}
onChangeVisibility(e){
this.setState({
diveVisibilityID: e.target.value,
});
}
onChangeDiveDate(e) {
this.setState({
diveDate: e.target.value,
});
}
onChangeMaxDepth(e){
this.setState({
diveMaxDepth: e.target.value,
});
}
onChangeDiverUserNumber(e){
this.setState({
diverUserNumber: e.target.value,
});
}
onChangeVerifiedBySchool(e){
this.setState({
diveVerifiedBySchool: e.target.value,
});
}
onChangeDiveNotes(e) {
this.setState({
diveNotes: e.target.value,
});
}
onChangeDivePoint(e){
this.setState({
divePoint: e.target.value,
});
}
handleSubmitDive(e) {
e.preventDefault();
this.setState({
loading: true,
});
this.form.validateAll();
//const {dispatch, history} = this.props;
if (this.checkBtn.context._errors.length === 0) {
this.props
.dispatch(registerUserDive(
this.state.diveTypeID,
this.state.diveSchoolNameID,
this.state.diveCurrentID,
this.state.diveVisibilityID,
this.state.diveDate,
this.state.diveMaxDepth,
this.state.diverUserNumber,
this.state.diveVerifiedBySchool,
this.state.diveNotes,
this.state.diveNotes))
.then(() => {
window.history.push("/divelogtable");
window.location.reload();
})
.catch(() => {
this.setState({
loading: false
});
});
}
}
render() {
const { classes } = this.props;
const { user: currentUser } = this.props;
if (this.state.currentList.length > 0) {
console.log("currentList", this.state.currentList);
}
if (this.state.regionList.length > 0) {
console.log("regionList", this.state.regionList);
}
if (this.state.diveTypeList.length > 0) {
console.log("diveTypeList", this.state.diveTypeList);
}
if (this.state.visibilityList.length > 0) {
console.log("visibilityList", this.state.visibilityList);
}
if (this.state.diveSpotList.length > 0) {
console.log("diveSpotList", this.state.diveSpotList);
}
return (
...materialUI form code
function mapStateToProps(state){
const { user } = state.auth;
const { regionList } = state.region;
const { currentList } = state.current;
const { diveTypeList } = state.diveType;
const { visibilityList } = state.visibility;
const { diveSpotList } = state.diveSpot;
return {
user,
regionList,
currentList,
diveTypeList,
visibilityList,
diveSpotList,
};
}
export default compose(
connect(
mapStateToProps,
),
withStyles(useStyles)
)(DiveLogForm);
Поскольку я в первую очередь занимаюсь добавлением данных моей формы в серверную часть. Я включил diveLog.service.js файл и т.д.
export const registerDive = (diveTypeID, diveSchoolNameID, diveCurrentID, diveVisibilityID, diveDate, diveMaxDepth, diveEquipmentWorn, diverUserNumber, diveVerifiedBySchool, diveNotes, divePoint) => {
return axios.post(API_URL "registerdive", {
diveTypeID,
diveSchoolNameID,
diveCurrentID,
diveVisibilityID,
diveDate,
diveMaxDepth,
diveVerifiedBySchool,
diveNotes,
divePoint
});
};
diveLog.action.js
export const registerUserDive = (
diveTypeID,
diveSchoolNameID,
diveCurrentID,
diveVisibilityID,
diveDate,
diveMaxDepth,
diverUserNumber,
diveVerifiedBySchool,
diveNotes,
divePoint) => (dispatch) => {
return registerDive(
diveTypeID,
diveSchoolNameID,
diveCurrentID,
diveVisibilityID,
diveDate,
diveMaxDepth,
diveVerifiedBySchool,
diveNotes,
divePoint).then(
(response) => {
dispatch ({
type: successful_reg,
});
dispatch({
type: set_message,
payload: response.data.message,
});
return Promise.resolve();
},
(error) => {
const message =
(error.response amp;amp;
error.response.data amp;amp;
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: set_message,
payload: message,
});
return Promise.resolve();
},
(error) => {
(error.response amp;amp;
error.response.data amp;amp;
error.response.data.message) ||
error.message ||
error.toString();
dispatch({
type: failed_reg,
});
return Promise.reject();
}
);
};
Мое действие с регистрацией diveLog, вероятно, будет неудачным, поскольку я не понимал концепцию редуктора при его кодировании.
Комментарии:
1.Ваш компонент — повторяющийся кошмар. Я мог бы возиться с этим только потому, что думаю, что около 60% из них можно удалить. Но я не понимаю вашего вопроса. Что будет сохранено в redux, зависит от вас. Прямо сейчас вы вносите изменения в redux только в одном месте, которое является
handleSubmitDive
методом, в котором выdispatch
registerUserDive
. Ничто в вашемcomponentDidMount
не изменяет состояние redux. Может быть, так и должно быть? Это зависит от вас.2. Исправлено: pastebin.com/BtrKLSza
Ответ №1:
Я не понимал вашего вопроса, пока не начал играть с кодом, но теперь я понимаю, что вы пытаетесь сделать. У вас есть пять разных списков ( regionList
, currentList
, и т.д.). Они, вероятно, используются для создания параметров выпадающего меню.
Прямо сейчас вы выбираете все списки из своего хранилища redux и предоставляете их в качестве реквизитов mapStateToProps
. Вы никогда не вносите никаких изменений в хранилище redux с вашими списками. Вы вызываете функции в своем componentDidMount
, чтобы извлекать данные списка из своего серверной части и сохранять эти данные this.state
. Это немного конфликтно, потому что теперь у нас есть данные в двух местах. Мы используем список из this.props
или тот, из this.state
которого?
В конечном счете, вам решать, какие данные вы хотите где хранить. Преимущество хранения данных в redux заключается в том, что они могут использоваться несколькими различными компонентами одновременно. Это также позволяет вам выполнять каждый вызов серверной части только один раз, но для того, чтобы воспользоваться этим преимуществом, вам нужно записывать вызовы с условной проверкой таким образом, чтобы вы выполняли вызов только в том случае, если данные еще не существуют.
Нужно ли мне также создавать редуктор для формы? или они отправляются прямо на серверную часть / узел / postgresql.
Я бы рекомендовал сохранить состояние формы в самом компоненте, поскольку частично заполненная форма используется только этим компонентом.
Я намерен создать таблицу, которая обновляется всеми самыми последними данными, чтобы я мог видеть логику ее автоматического добавления к полученным данным.
Я не уверен, что является родительским для чего, но если эта форма отображается на экране с таблицей, тогда вы можете захотеть переместить isLoading
состояние до родительского и обновить его с помощью обратного вызова, переданного в props. Таким образом, компонент таблицы знает, когда он загружает новую строку. Или, может быть, вы отправляете действие для сохранения нового погружения в redux при нажатии отправки (но я бы не стал сохранять его при каждом нажатии клавиши).
В этом компоненте я извлекаю данные из своего бэкэнда для полей таблицы в методе componentDidMount, и я не уверен, сохраняются ли в Redux только данные, которые должны быть изменены.
Universal data — хороший кандидат для redux. Так что, на мой взгляд, что-то вроде списка всех регионов имеет смысл хранить в redux.
Я пытаюсь разобраться, для чего мне нужно создавать редукторы / действия.
Когда у вас есть пять разных списков, которые действуют одинаково, полезно определить обобщенные действия и создателей действий, которые принимают имя списка в качестве переменной. Было бы неплохо также иметь обобщенную pullFields
функцию!
Это своего рода второстепенное замечание, но рекомендуется, чтобы все, кто только начинает, изучали функциональные компоненты и хуки useSelector
, useDispatch
а не компоненты класса и connect
. Написание компонентов стало намного проще, и некоторых вещей, которые вы делаете, this.handleSubmitDive.bind(this)
можно легко избежать.
Я предпринял попытку убрать повторения в вашем коде, но я не ответил на вопросы о сокращении. Итак, вот рекомендуемая настройка для обработки выборки данных с помощью redux. Некоторые из них немного «продвинутые», но я думаю, вы сможете просто скопировать и вставить их.
Определите асинхронное действие thunk, которое извлекает данные списка из вашего API и сохраняет их в redux, но ничего не делает, если данные уже были загружены.
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
export const requireFieldData = createAsyncThunk(
'fields/requireData', // action name
// action expects to be called with the name of the field
async (field) => {
// you need to define a function to fetch the data by field name
const response = await pullField(field);
const { data } = response;
// what we return will be the action payload
return {
field,
items: data.data
};
},
// only fetch when needed: https://redux-toolkit.js.org/api/createAsyncThunk#canceling-before-execution
{
condition: (field, {getState}) => {
const {fields} = getState();
// check if there is already data by looking at the array length
if ( fields[field].length > 0 ) {
// return false to cancel execution
return false;
}
}
}
)
...
Редуктор для полей, в котором хранятся данные, полученные из API. (с использованием createSlice)
...
const fieldsSlice = createSlice({
name: 'fields',
initialState: {
current: [],
region: [],
diveType: [],
visibility: [],
diveSpot: [],
},
reducers: {},
extraReducers: {
// picks up the success action from the thunk
[requireFieldData.fulfilled.type]: (state, action) => {
// set the property based on the field property in the action
state[action.payload.field] = action.payload.items
}
}
})
export default fieldsSlice.reducer;
Пользовательский редуктор должен иметь возможность добавлять погружение. Вероятно, вы хотите сохранить гораздо больше информации и выполнить здесь гораздо больше действий.
const userSlice = createSlice({
name: 'user',
initialState: {
dives: [],
},
reducers: {
// expects action creator to be called with a dive object
addDive: (state, action) => {
// append to the dives array
state.dives.push(action.payload)
}
}
})
export const { addDive } = userSlice.actions;
export default userSlice.reducer;
Базовая настройка хранилища, объединение fields
и user
срезы
import { configureStore } from "@reduxjs/toolkit";
import fieldsReducer from "./fields";
import userReducer from "./user";
export default configureStore({
// combine the reducers
reducer: {
user: userReducer,
fields: fieldsReducer,
}
});
Компонент может использовать useSelector
вместо mapStateToProps
доступа к данным из redux. Мы dispatch
выполним действие thunk, чтобы убедиться, что все списки загружены. Они будут начинаться как пустые массивы, но будут обновлены до нового значения по завершении действия.
const DiveLogForm = (props) => {
// select user object from redux
const user = useSelector(state => state.user);
// get the object with all the fields
const fields = useSelector(state => state.fields);
// can destructure individual fields
const { current, region, diveType, visibility, diveSpot } = fields;
// state for the current field value
const [dive, setDive] = useState({
typeID: "",
schoolNameID: "",
currentID: "",
visibilityID: "",
date: "",
maxDepth: "",
userNumber: "",
verifiedBySchool: "",
notes: "",
point: "",
});
// all onChange functions do the exact same thing, so you only need one
// pass to a component like onChange={handleChange('typeID')}
const handleChange = (property) => (e) => {
setDive({
// override the changed property and keep the rest
...dive,
[property]: e.target.value,
});
}
// get access to dispatch
const dispatch = useDispatch();
// useEffect with an empty dependency array is the same as componentDidMount
useEffect(() => {
// dispatch the action to load fields for each field type
// once loaded, the changes will be reflected in the fields variable from the useSelector
Object.keys(fields).forEach(name => dispatch(requireFieldData(name)));
}, []); // <-- empty array
const handleSubmitDive = (e) => {
// do some stuff with the form
// do we need to save this to the backend? or just to redux?
dispatch(addDive(dive));
}
return (
<form />
)
}
Комментарии:
1. Большое спасибо за предоставление такого подробного ответа. Ранее я создал обобщенный API контроллера в своем бэкэнде, чтобы возвращать все поля за один раз, но вернулся к выполнению их по отдельности, когда не смог заставить его работать. Я собираюсь вернуться обратно.
2. Если я добавляю хранилище конфигурации объединенного редуктора, включаю ли я его в другие мои объединенные редукторы / отдельный файл хранилища redux или его собственный файл для формы diveLog?
3. Вам нужно только одно хранилище redux в вашем приложении. Он должен включать все ваши редукторы.