react-codemod icon indicating copy to clipboard operation
react-codemod copied to clipboard

Render Prop Expression to Child Component Transformer code

Open davidtingsu opened this issue 3 years ago • 0 comments

I have working code to make the render prop expression as a child component.

Please let me know if this transformer code would be useful to add to the react-codemod code base.

My use case was that the Formik library is deprecating the use of render prop in their API. https://formik.org/docs/api/formik#render-props-formikpropsvalues--reactnode

https://astexplorer.net/#/gist/d3df017f80a840c55c4f7041154c0a0d/8a482bb109f7c5e34fc8ab43296f797991d1989b Downloadable file: https://gist.github.com/d3df017f80a840c55c4f7041154c0a0d/8a482bb109f7c5e34fc8ab43296f797991d1989b

Here is the transformer code:

const isRenderProp = (object) => {
  return object.name && object.name.name == "render";
};
const hasRenderProp = (path) => {
  return path && path.value && path.value.openingElement && path.value.openingElement.attributes.filter(isRenderProp).length > 0;
};
const update = (api) => (path) => {
  const renderProp = path.value.openingElement.attributes.filter(isRenderProp)[0];
  // remove render prop
  path.value.openingElement.attributes = path.value.openingElement.attributes.filter((path) => !isRenderProp(path));
  // make field a non closing field

  path.value.openingElement.selfClosing = false;

  path.value.closingElement = api.jscodeshift.jsxClosingElement(path.value.openingElement.name);
  // make element in render prop now a child of the react element
  if (renderProp && renderProp.value && renderProp.value.expression) {
    path.value.children.push(renderProp.value);
  }
  return path;
};

export default function transformer(file, api) {
  const j = api.jscodeshift;

  const root = j(file.source);
  root
    .find(j.JSXElement, {
      closingElement: null
    })
    .filter(hasRenderProp)
    .forEach(update(api));
  return root.toSource();
}

Example Input:

const App = () => {
  <FieldArray
    name={orderLinesFieldName}
    render={(arrayHelpers) => {
      const priceFieldName = `${orderLinesFieldName}[${i}].${priceFieldConst}`;

      return (
        <Field
          name={priceFieldName}
          render={({ form, field }) => {
            const error = getIn(form.errors, field.name);

            return (
              <CenteredCell>
                <Block>USD</Block>
                <Input
                  {...(error ? { error: true } : {})}
                  type="number"
                  overrides={{
                    Input: {
                      props: {
                        "data-testid": "existing-adjustment-amount",
                      },
                    },
                  }}
                  disabled={actionItemStatus === statusClosed}
                  value={orderLines[i].price}
                  onChange={(e) => {
                    arrayHelpers.replace(i, {
                      ...orderLines[i],
                      price: e.target.value,
                    });
                  }}
                />
              </CenteredCell>
            );
          }}
        />
      );

Example output

const App = () => {
 <FieldArray name={orderLinesFieldName}>{(arrayHelpers) => {
     const priceFieldName = `${orderLinesFieldName}[${i}].${priceFieldConst}`;

     return (
       (<Field name={priceFieldName}>{({ form, field }) => {
           const error = getIn(form.errors, field.name);

           return (
             <CenteredCell>
               <Block>USD</Block>
               <Input
                 {...(error ? { error: true } : {})}
                 type="number"
                 overrides={{
                   Input: {
                     props: {
                       "data-testid": "existing-adjustment-amount",
                     },
                   },
                 }}
                 disabled={actionItemStatus === statusClosed}
                 value={orderLines[i].price}
                 onChange={(e) => {
                   arrayHelpers.replace(i, {
                     ...orderLines[i],
                     price: e.target.value,
                   });
                 }}
               />
             </CenteredCell>
           );
         }}</Field>)
     );
   }}</FieldArray>;
};

More about render props here: https://reactjs.org/docs/render-props.html

davidtingsu avatar Apr 27 '21 16:04 davidtingsu