我需要将哪些状态数据传递给 mapStateToProps

What state data do I need to pass to mapStateToProps

我第一次尝试使用 Redux 在我的应用程序中放入一个表单组件,我正在努力了解我必须为其创建 Reducers/Actions 的内容。

在其他组件中,我将我的用户和消息传递到 mapStateToProps 并且它们可以正常工作。然而,在这个组件中,我正在从我的后端为 componentDidMount 方法中的 table 字段提取数据,我不确定是否只有要更改的数据存储在 Redux 中。

我是否也需要为表单创建一个缩减器?还是直接发布到后端/节点/postgresql。我打算有一个 table 来更新所有最新数据,这样我就可以看到它被自动添加到检索到的数据的逻辑。

我是 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 文件 etc

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 &&
                    error.response.data &&
                    error.response.data.message) ||
                error.message ||
                error.toString();
            dispatch({
                type: set_message,
                payload: message,
            });

            return Promise.resolve();
        },
        (error) => {
            (error.response &&
                error.response.data &&
                error.response.data.message) ||
            error.message ||
            error.toString();

            dispatch({
                type: failed_reg,
            });
            return Promise.reject();
        }
    );
};

我的 diveLog 注册操作可能有点偏离,因为我在编码时不理解 reducer 概念。

在我开始玩代码之前我不明白你的问题,但现在我明白你想做什么了。您有五个不同的列表(regionListcurrentList 等)。这些可能用于生成下拉菜单的选项。

现在您正在从您的 redux 商店中选择所有列表,并通过 mapStateToProps 将它们作为 props 提供。您永远不会使用列表将任何更改推送到 redux 存储。您正在 componentDidMount 中调用函数以从后端获取列表数据并将该数据存储在 this.state 中。这有点冲突,因为现在我们有两个地方的数据。我们是使用 this.props 中的列表,还是 this.state 中的列表?

最终由您决定要将哪些数据存储在何处。将数据存储在 redux 中的好处是它可以同时被多个不同的组件使用。它还允许您对后端进行一次调用,但为了获得这一优势,您需要编写带有条件检查的调用,以便仅在数据尚不存在时才进行调用。

Do I need to create a reducer for the form as well? or does it get posted straight to the backend / node / postgresql.

我建议将表单状态保留在组件本身中,因为部分填写的表单仅供此组件使用。

I intend to have a table that updates with all the most recent data so I can see the logic of it being automatically added to retrieved data.

我不确定什么是什么的父级,但是如果此表单显示在带有 table 的屏幕上,那么您可能希望将 isLoading 状态移至父级并通过传递给 props 的回调对其进行更新。这样 table 组件就知道它何时加载新行。或者,当你点击提交时,你可能会发送一个动作来将新的潜水存储到 redux(但我不会在每次击键时都存储它)。

In this component I am pulling data from my backend for table fields in the componentDidMount method and I am not sure if it is only data that is to be changed that get stored in Redux.

通用数据是 redux 的一个很好的候选者。所以在我看来,像所有地区的列表这样的东西存储在 redux 中是有意义的。

I am trying to get my head around what I have to create Reducers / Actions for.

当您有五个行为相似的不同列表时,最好定义将列表名称作为变量的通用操作和操作创建者。最好也有一个通用的 pullFields 函数!

这有点旁注,但建议任何刚起步的人都应该学习函数组件和钩子 useSelectoruseDispatch 而不是 class 组件和connect。编写组件变得容易多了,您正在做的一些事情 this.handleSubmitDive.bind(this) 可以很容易地避免。

我尝试在您的代码中 clean up the repetitions 但我没有解决 redux 问题。所以这里有一个使用 redux 处理数据获取的建议设置。其中一些有点“高级”,但我认为您应该能够复制并粘贴它。

定义一个 async thunk action,它从您的 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;

用户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;

加入 fieldsuser 片的基本商店设置

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 />
  )
}