GraphSitter exposes several React and JSX-specific APIs for working with modern React codebases.
Key APIs include:
Detecting React Components with is_jsx
Codegen exposes a is_jsx
property on both classes and functions, which can be used to check if a symbol is a React component.
# Check if a function is a React component
function = file.get_function("MyComponent")
is_component = function.is_jsx # True for React components
# Check if a class is a React component
class_def = file.get_class("MyClassComponent")
is_component = class_def.is_jsx # True for React class components
Working with JSX Elements
Given a React component, you can access its JSX elements using the jsx_elements property.
You can manipulate these elements by using the JSXElement and JSXProp APIs.
# Get all JSX elements in a component
for element in component.jsx_elements:
# Access element name
if element.name == "Button":
# Wrap element in a div
element.wrap("<div className='wrapper'>", "</div>")
# Get specific prop
specific_prop = element.get_prop("className")
# Iterate over all props
for prop in element.props:
if prop.name == "className":
# Set prop value
prop.set_value('"my-classname"')
# Modify element
element.set_name("NewComponent")
element.add_prop("newProp", "{value}")
# Get child JSX elements
child_elements = element.jsx_elements
# Wrap element in a JSX expression (preserves whitespace)
element.wrap("<div className='wrapper'>", "</div>")
Common React Operations
Refactoring Components into Separate Files
Split React components into individual files:
# Find (named) React components
react_components = [
func for func in codebase.functions
if func.is_jsx and func.name is not None
]
# Filter out those that are not the default export
non_default_components = [
comp for comp in react_components
if not comp.export or not comp.export.is_default_export()
]
# Move these non-default components to new files
for component in react_components:
if component != default_component:
# Create new file
new_file_path = '/'.join(component.filepath.split('/')[:-1]) + f"{component.name}.tsx"
new_file = codebase.create_file(new_file_path)
# Move component and update imports
component.move_to_file(new_file, strategy="add_back_edge")
Updating Component Names and Props
Replace components throughout the codebase with prop updates:
# Find target component
new_component = codebase.get_symbol("NewComponent")
for function in codebase.functions:
if function.is_jsx:
# Update JSX elements
for element in function.jsx_elements:
if element.name == "OldComponent":
# Update name
element.set_name("NewComponent")
# Edit props
needs_clsx = not file.has_import("clsx")
for prop in element.props:
if prop.name == "className":
prop.set_value('clsx("new-classname")')
needs_clsx = True
elif prop.name == "onClick":
prop.set_name('handleClick')
# Add import if needed
if needs_clsx:
file.add_import_from_import_source("import clsx from 'clsx'")
# Add import if needed
if not file.has_import("NewComponent"):
file.add_import(new_component)