0

I have a Fields component that I am trying to use sometimes by itself and sometimes from inside a FieldArray component. I have added a snippet below with a simplified model.

import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, combineReducers } from 'redux';
import { Provider } from 'react-redux';
import { reduxForm, Fields, FieldArray, reducer as formReducer } from 'redux-form';

const reducers = {
  form: formReducer
};

const reducer = combineReducers(reducers);

const store = createStore(reducer);

const renderSharedComponent = (fields) => {
  console.log(fields);
  return (<div>Shared Component</div>);
};

const renderHashes = ({ fields }) => (
  <div>
    {
      fields.map((field) => (
        <Fields
          key={ field }
          names={ [`${field}.value`, `${field}.valueIsRegex`] }
          component={ renderSharedComponent }
        />
      ))
    }
  </div>
);

const ReactComponent = () => (
  <div>
    <FieldArray
      name="hashes"
      component={ renderHashes }
    />
    <Fields
      names={ ['value', 'valueIsRegex'] }
      component={ renderSharedComponent }
    />
  </div>
);

const ReduxForm = reduxForm({
  form: 'default',
  initialValues: {
    hashes: [{}]
  }
})(ReactComponent);

ReactDOM.render((
  <div>
    <Provider store={ store }>
      <ReduxForm />
    </Provider>
  </div>
), document.getElementById('content'));

When I use the Fields component by itself, the fields argument from inside renderSharedComponent has the following form:

{
  value: { input: {...}, meta: {...} },
  valueIsRegex: { input: {...}, meta: {...} },
  names: [ 'value' , 'valueIsRegex' ]
} 

When I use the Fields component inside a FieldArray component, the fields argument from inside renderSharedComponent has the following form:

{
  hashes: [
      {
        value: { input: {...}, meta: {...} },
        valueIsRegex: { input: {...}, meta: {...} }
      }
  ],
  names: [ 'hashes[0].value' , 'hashes[0].valueIsRegex' ]
} 

If I will be using the Fields component inside a FieldArray component with a different name (let's say paths) the names property will change accordingly (eg. names: [ 'paths[0].value' , 'paths[0].valueIsRegex' ]).

I am trying to get the value and valueIsRegex objects in a generic way that will support any of the cases I presented above.

Right now I have created a function where I use a RegEx to determine the fields. But I was wondering if anyone knows a better way to do this (maybe there is a Redux Form util that maybe I missed when reading the documentation).

2 Answers 2

1

having the same problem. It may be that the idiomatic way is to use the formValueSelector function. But that way its a bit more boilerplate, as you have to pass the selector (as far as I understood it) all the way down through the form. Personally I did the regexp-based function as well. Here it is:

/**
 * Converts path expression string to array
 * @param {string} pathExpr path string like "bindings[0][2].labels[0].title"
 * @param {Array<string|int>} array path like ['bindings', 0, 2, 'labels', 0, 'title']
 */
function breakPath(pathExpr) {
    return pathExpr
        .split(/\]\.|\]\[|\[|\]|\./)
        .filter(x => x.length > 0)
        .map(x => isNaN(parseInt(x)) ? x : parseInt(x));
}

/**
 * Executes path expression on the object
 * @param {string} pathExpr – path string like "bindings[0][2].labels[0].title"
 * @param {Object|Array} obj - object or array value
 * @return {mixed} a value lying in expression path
 * @example 
 * ```
 * execPath('books[0].title', {books: [{title: 'foo'}]})
 * // yields
 * 'foo' 
 * ```
 */
function execPath(pathExpr, obj) {
    const path = breakPath(pathExpr);
    if (path.length < 1) {
        return obj;
    }
    return path.reduce(function(obj, pathPart) {
        return obj[pathPart];
    }, obj);
}

/**
 * A generic GroupLabelField that relies on valueBasePath
 */
const GroupLabelField = (props) => {
    const groupData = execPath(props.valueBasePath, props);
    return (
        <div className={`label__content label__content_icon_${groupData.objectType.input.value}`}>
            <span className="label__remove"
                  onClick={(e) => { props.handleRemove(); e.stopPropagation(); }}
            >
                <i className="material-icons material-icons_12 material-icons_top">&#xE5CD;</i>
            </span>
            <span className="label__text">{groupData.title.input.value}</span>
        </div>
    );
};

Sign up to request clarification or add additional context in comments.

Comments

1

redux-form has a hidden utility function which is useful here, but I don't know if you can rely on it's availability in future versions:

import structure from "redux-form/lib/structure/plain";

function RenderRow({ names, ...props }) {
  const fields = {};
  names.forEach(n => (fields[n] = structure.getIn(props, n)));
}

See also this github issue

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.