每个组件实例的React / Redux-Individual状态

时间:2016-11-23 10:31:45

标签: reactjs redux react-redux redux-form

如果有一个用户列表,每个条目都有一个按钮»编辑«。如果用户点击它,则会发生以下情况:

  1. 向服务器请求表单
  2. 将组件<UserEditForm />添加到条目中,扩展条目
  3. 除了一件事之外,这个工作正常:如果再单击一个按钮,表单的每个实例都会收到所请求的最后一个用户表单的数据。那是因为我在该州只有一个userform属性。

    所以要解决这个问题,我想将userform交换到userforms,这应该/可能是一个像这样的对象:

    userforms: {
      <id of first instance>: { … }, //formdata
      <id of second instance>: { … },
      …
    }
    

    但是因为我是React / Redux的新手,所以我真的不知道如何做到这一点,或者说“正确”的做法,或者最佳做法是实际做到这一点。

    我的想法是创建一个更高级的组件,如下所示:

    import React from 'react';
    import {connect} from 'react-redux';
    import {uuid} from '../../helpers/uuid';
    
    export const formdatainstance = (FormInstance) => {
    
      let id = null;
    
      class FormDataMapper extends React.Component {
        constructor (props) {
          super(props);
          id = uuid();
        }
    
        render () {
          //extract the formdata from the state
          //using the id
          return <FormInstance { ...this.props } />
        }
      }
    
      const mapStateToProps = (state) => {
        console.log(id); //is null for one run
        return {
          userforms: state.userforms
        };
      };
    
      return connect(mapStateToProps)(FormDataMapper);
    }
    

    所以在List组件中我可以:

    import UserEditForm from './UserEditForm';
    import {formdatainstance} from './formdatainstance';
    
    const MappedUserEditForm = formdatainstance(UserEditForm);
    
    class List extends React.Component {
      render(){
        return (
          {users.map(user => {
            //more stuff
            <MappedUserEditForm />
            //more stuff
          })}
        );
      }
    }
    

    所以我的问题:这是一个好主意吗?如果是,那么进行清理的正确方法是什么,所以当在组件的生命周期中我应该从状态中删除数据?还有另一种方法可以做到这一点,这更容易吗?

    感谢您的帮助!

2 个答案:

答案 0 :(得分:1)

这是你可以做的......

import React from 'react';
import { compose } from 'redux';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';

class UserEditForm extends Component {
   ...

   render() {
      return <form onSubmit={this.props.handleSubmit(this.props.onSubmit)}>
          ...form fields
      </form>
   }
}

const mapStateToProps = (state, ownProps) => {
   return {
      form: ownProps.formId
   }
}

export default compose(
   connect(mapStateToProps),
   reduxForm({
      //...other redux-form options
   })
)(UserEditForm);

您的ListComponent

render() {
   return <ul>
      {this.props.users.map(user => 
         <li key={user.id}>
             ...
             <UserEditForm formId={'form-' + user.id} onSubmit={...} />
         </li>
      )}
   </ul>
}

这允许您拥有动态表单名称。

答案 1 :(得分:0)

即使@jpdelatorre的答案对我来说似乎是最受欢迎的,因为它还包含了redux-forms的链接,这可能对我有很大的帮助,我想在这里发布我的工作解决方案,只是在有人可能会发现它很有用。它只是打了我一夜,所以需要测试我的想法是否正确,我最终可以证明。

我无法使用唯一的HOC进行整个映射,我也需要添加/修改Reducer。基本上它的工作原理如下:

  1. 数据映射由ID

  2. 完成
  3. 包装原始动作创建者,以便将使用的ID附加到对象

  4. 减速器被包裹两个并由»datamapped«reducer

  5. 调用

    因此原始Reducer和action creators的代码不需要更改,是什么让包装类型易于使用。我首先想要使用动态创建的uuid,但我放弃了它,以便保存和恢复整个应用程序状态。

    所以HOC代码就是:

    import React from 'react';
    import {connect} from 'react-redux';
    
    // The Component to wrap,
    // all of its actions
    // its default state
    export const formdatainstance = (FormInstance, Actions, defaultState = {}) => {
    
      const mapStateToProps = (state) => {
        return {
          mappedData: state.mappedData
        };
      };
    
      class FormDataMapper extends React.Component {
        static propTypes = {
          id: React.PropTypes.string.isRequired
        };
    
        static contextTypes = {
          store: React.PropTypes.object
        };
    
        //most of mapping happens here
        render () {
          //wrap the action creators
          const actions = Object.keys(Actions).reduce((list, key) =>{
            list[key] = (...args) => {
              const action = Actions[key](...args);
    
              //handle asyn operations as well
              if('then' in action && typeof action['then'] == 'function') {
                action.then(data => {
                  //attaching the id
                  this.props.dispatch({...data, id: this.props.id});
                });
              } else {
                //attach the id
                this.context.store.dispatch({...action, id: this.props.id });
              }
            };
            return list;
          }, {}),
            //there wont be any data at first, so the default state is handed
            //over
            mappedProps = this.props.mappedData.hasOwnProperty(this.props.id) ?
              this.props.mappedData[this.props.id] : defaultState;
    
          //merge the hotchpotch
          let props = Object.assign({}, mappedProps, this.props, actions);
    
          //clean up
          delete props.id;
          delete props.mappedData;
    
          return <FormInstance { ...props } />
        }
      }
    
      return connect(mapStateToProps)(FormDataMapper);
    };
    

    reducer代码:

    //hlper method
    export const createTypesToReducerMap = (types, reducer) => {
      return Object.keys(types).reduce((map, key) => {
        map[types[key]] = reducer;
        return map;
      }, {});
    }
    
    
    export const createMappedReducer = (reducerMap, defaultState = {}) => {
      const HANDLERS = reducerMap.reduce((handlers, typeMap) => {
        return { ...handlers, ...typeMap };
      },{});
    
      return (state, action) => {
    
        if (!action.hasOwnProperty('id')) {
          if (state === undefined) return defaultState;
          return state;
        }
    
        const reducer = HANDLERS.hasOwnProperty(action.type) ?
              HANDLERS[action.type] : null;
    
        let a = {...action};
        delete a.id;
    
    
        return reducer !== null ?
            Object.assign({}, state, { [action.id]: reducer(state[action.id], a)}) :
            state;
      }
    }
    

    最后是商店:

    const userEditTypeReducerMap = createTypesToReducerMap(userEditTypes, userFormReducer);
    
    
    const reducer = combineReducers({
      …
      mappedData: createMappedReducer(
        [userEditTypeReducerMap], {})
      …
    });
    
    
    export default compose(
      applyMiddleware(
        thunk
      )
    )(createStore)(reducer, {});