Skip to main content

React & JSX

Props

Inserting props

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source
.find(j.JSXElement)
// Find all components called button
.filter(path => path.value.openingElement.name.name === 'button')
.forEach(element => {
const newComponent = j.jsxElement(
j.jsxOpeningElement(element.node.openingElement.name, [
...element.node.openingElement.attributes,
// build and insert our new prop
j.jsxAttribute(j.jsxIdentifier('disabled'), j.stringLiteral('true')),
]),
element.node.closingElement,
element.node.children,
);

// Replace our original component with our modified one
j(element).replaceWith(newComponent);
});

return source.toSource();
}

Input:

import React from 'react';

const Button = props => {
return <button className="button">Submit</button>;
};

Output:

import React from 'react';

const Button = props => {
- return <button className="button">Submit</button>;
+ return <button className="button" disabled="true">Submit</button>;
};

Removing props

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source
.find(j.JSXElement)
.filter(path => path.value.openingElement.name.name === 'button') // Find all button jsx elements
.find(j.JSXAttribute) // Find all attributes (props) on the button
.filter(path => path.node.name.name === 'onClick') // Filter to only props called onClick
.remove(); // Remove everything that matched

return source.toSource();
}

Input:

import React from 'react';

const Button = props => {
return (
<button className="button" onClick={() => console.log('Hello, World!')}>
Submit
</button>
);
};

Output:

import React from 'react';

const Button = props => {
- return <button className="button" onClick={() => console.log('Hello, World!')}>Submit</button>;
+ return <button className="button">Submit</button>;
};

Updating props

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source
.find(j.JSXElement)
.filter(path => path.value.openingElement.name.name === 'button') // Find all button jsx elements
.find(j.JSXAttribute) // Find all attributes (props) on the button
.filter(attribute => attribute.node.name.name === 'className') // Filter to only props called className
.find(j.Literal) // Narrow further to the literal value (note: This may not always be a Literal)
.forEach(literal => {
literal.node.value = literal.node.value + ' button-primary';
}); // Mutate the value using the current value

return source.toSource();
}

Input:

import React from 'react';

const Button = props => {
return <button className="button">Submit</button>;
};

Output:

import React from 'react';

const Button = props => {
- return <button className="button">Submit</button>;
+ return <button className="button button-primary">Submit</button>;
};

Renaming props

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source
.find(j.JSXElement)
.filter(path => path.value.openingElement.name.name === 'button') // Find all button jsx elements
.find(j.JSXAttribute) // Find all attributes (props) on the button
.filter(attribute => attribute.node.name.name === 'data-foo') // Filter to only props called data-foo
.forEach(attribute =>
j(attribute).replaceWith(
j.jsxAttribute(j.jsxIdentifier('data-bar'), attribute.node.value),
),
); // Reconstruct the attribute, replacing the name and keeping the value

return source.toSource();
}

Input:

import React from 'react';

const Button = props => {
return <button data-foo="bar">Submit</button>;
};

Output:

import React from 'react';

const Button = props => {
- return <button data-foo="bar">Submit</button>;
+ return <button data-bar="bar">Submit</button>;
};

Rest props

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);
const props = [];

source
// Find all jsx elements with the name "button"
.find(j.JSXElement)
.filter(path => path.node.openingElement.name.name === 'button')
// Collect all of their props
.forEach(path => props.push(...path.node.openingElement.attributes))
// Now get all of the jsx attributes (props)...
.find(j.JSXAttribute)
// And replace them with a spread attribute called "props" for example `{...props}`
.forEach(path => path.replace(j.jsxSpreadAttribute(j.identifier('props'))));

// Create a new constant variable named props.
const variableDeclaration = j.variableDeclaration('const', [
j.variableDeclarator(
j.identifier('props'),
// the variable will be assigned an object containing all of the props from button
j.objectExpression(
props.map(prop =>
j.objectProperty(
j.identifier(prop.name.name),
j.stringLiteral(prop.value.value),
),
),
),
),
]);

// Finally, we find the arrow function expression
source
.find(j.ArrowFunctionExpression)
// We then retrieve its body, which is the "block scope" of the component
.get('body')
// Since elements in a block are an array, we need to insert our new variable using unshift because we want it to be first
.value.body.unshift(variableDeclaration);

return source.toSource();
}

Input:

import React from 'react';

const Button = () => {
return <button className="button">Submit</button>;
};

Output:

import React from 'react';

const Button = () => {
+ const props = { className: 'button' };
- return <button className="button">Submit</button>;
+ return <button {...props}>Submit</button>;
};

JSX

Wrapping components

Wrapping react components with react components is a fairly common operation.

Simply follow this fairly simple set of steps:

  1. Find the component you want to wrap
  2. Create a new component and pass the component to be wrapped in as a child node
  3. Replace the original component with a wrapped version of itself

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

// Find all components named "Avatar"
source.findJSXElements('Avatar').forEach(element => {
// Create a new JSXElement called "Tooltip" and use the original Avatar component as children
const wrappedAvatar = j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier('Tooltip'), [
// Create a prop on the tooltip so it works as expected
j.jsxAttribute(
j.jsxIdentifier('content'),
j.stringLiteral('Hello, there!'),
),
]),
j.jsxClosingElement(j.jsxIdentifier('Tooltip')),
[element.value], // Pass in the original component as children
);

j(element).replaceWith(wrappedAvatar);
});

return source.toSource();
}

Input:

import { Avatar, Tooltip } from 'component-lib';

const App = () => {
return <Avatar name="foo" />;
};

Output:

import {Avatar, Tooltip } from 'component-lib';

const App = () => {
return (
+ <Tooltip content="foo">
<Avatar name="foo" />
+ </Tooltip>
);
}

Inserting children nodes

Transform:

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source
.find(j.JSXElement) // Find all jsx elements
.filter(path => path.node.openingElement.name.name === 'ul') // filter to an array of only ul elements
.forEach(path =>
// Replace each ul element with a modified version of itself
path.replace(
j.jsxElement(path.node.openingElement, path.node.closingElement, [
...path.node.children, // Copy existing children
// Create a new li element containing our new entry
j.jsxElement(
j.jsxOpeningElement(j.jsxIdentifier('li')),
j.jsxClosingElement(j.jsxIdentifier('li')),
[j.stringLiteral('Venusaur')],
),
j.jsxText('\n'), // Add this to tidy up the formatting
]),
),
);

return source.toSource();
}

Input:

import React from 'react';

const Button = () => {
return (
<ul>
<li>Bulbasaur</li>
<li>Ivysaur</li>
</ul>
);
};

Output:

import React from 'react';

const Button = () => {
return (
<ul>
<li>Bulbasaur</li>
<li>Ivysaur</li>
+ <li>Venusaur</li>
</ul>
);
};

Render props

Moving between different types of React composition strategies, like for example, from component props to render props is could be something you want to do between major versions. This might seem difficult on the surface, but think about it like every other codemod. First we need to find the component, replace it with a modified copy of itself and finally insert a function as children.

Transform:

import { getJSXAttributes } from '@codeshift/utils';

export default function transformer(file, { jscodeshift: j }, options) {
const source = j(file.source);

source.findJSXElements('Avatar').forEach(element => {
// Find props all JSXAttributes with a prop called "component"
// (Using the getJSXAttributeByName util here for simplicity)
const componentProp = getJSXAttributes(j, element, 'component').get();
// Grabs the name of the component passed into the "component" prop
const componentName = j(componentProp)
.find(j.JSXExpressionContainer)
.find(j.Expression)
.get().value.name;

// Remove it since it's no longer required on the wrapping component
j(componentProp).remove();

// Create a new child component based on the component prop and spread props onto it
const customComponent = j.jsxElement(
j.jsxOpeningElement(
j.jsxIdentifier(componentName),
[j.jsxSpreadAttribute(j.identifier('props'))],
true,
),
);

/**
* Here's where it gets interesting.
* We create a render prop function and pass in `customComponent` as the return value
*/
const childrenExpression = j.jsxExpressionContainer(
j.arrowFunctionExpression([j.identifier('props')], customComponent),
);

/**
* Then finally, we replace our original component with the following.
* Taking properties from the original component and combining them with our new render prop function
*/
j(element).replaceWith(
j.jsxElement(
j.jsxOpeningElement(
element.value.openingElement.name,
element.value.openingElement.attributes,
false,
),
j.jsxClosingElement(element.value.openingElement.name),
[childrenExpression],
),
);
});

return source.toSource();
}

Input:

import Avatar from '@component-lib/avatar';

const App = () => {
return <Avatar name="Dan" component={CustomAvatar} />;
};

Output:


import Avatar from '@component-lib/avatar';

const App = () => {
return (
- <Avatar name="Dan" component={CustomAvatar} />
+ <Avatar name="Dan">{props => <CustomAvatar {...props} />}</Avatar>
+ );
}