Skip to main content

Documentation Index

Fetch the complete documentation index at: https://codegeninc-fix-system-prompt-typo.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Codegen SDK provides powerful APIs for modernizing React codebases. This guide will walk you through common React modernization patterns. Common use cases include:
  • Upgrading to modern APIs, including React 18+
  • Automatically memoizing components
  • Converting to modern hooks
  • Standardizing prop types
  • Organizing components into individual files
and much more.

Converting Class Components to Functions

Here’s how to convert React class components to functional components:
# Find all React class components
for class_def in codebase.classes:
    # Skip if not a React component
    if not class_def.is_jsx or "Component" not in [base.name for base in class_def.bases]:
        continue

    print(f"Converting {class_def.name} to functional component")

    # Extract state from constructor
    constructor = class_def.get_method("constructor")
    state_properties = []
    if constructor:
        for statement in constructor.code_block.statements:
            if "this.state" in statement.source:
                # Extract state properties
                state_properties = [prop.strip() for prop in
                    statement.source.split("{")[1].split("}")[0].split(",")]

    # Create useState hooks for each state property
    state_hooks = []
    for prop in state_properties:
        hook_name = f"[{prop}, set{prop[0].upper()}{prop[1:]}]"
        state_hooks.append(f"const {hook_name} = useState(null);")

    # Convert lifecycle methods to effects
    effects = []
    if class_def.get_method("componentDidMount"):
        effects.append("""
    useEffect(() => {
        // TODO: Move componentDidMount logic here
    }, []);
    """)

    if class_def.get_method("componentDidUpdate"):
        effects.append("""
    useEffect(() => {
        // TODO: Move componentDidUpdate logic here
    });
    """)

    # Get the render method
    render_method = class_def.get_method("render")

    # Create the functional component
    func_component = f"""
const {class_def.name} = ({class_def.get_method("render").parameters[0].name}) => {{
    {chr(10).join(state_hooks)}
    {chr(10).join(effects)}

    {render_method.code_block.source}
}}
"""

    # Replace the class with the functional component
    class_def.edit(func_component)

    # Add required imports
    file = class_def.file
    if not any("useState" in imp.source for imp in file.imports):
        file.add_import("import { useState, useEffect } from 'react';")

Migrating to Modern Hooks

Convert legacy patterns to modern React hooks:
# Find components using legacy patterns
for function in codebase.functions:
    if not function.is_jsx:
        continue

    # Look for common legacy patterns
    for call in function.function_calls:
        # Convert withRouter to useNavigate
        if call.name == "withRouter":
            # Add useNavigate import
            function.file.add_import(
                "import { useNavigate } from 'react-router-dom';"
            )
            # Add navigate hook
            function.insert_before_first_return("const navigate = useNavigate();")
            # Replace history.push calls
            for history_call in function.function_calls:
                if "history.push" in history_call.source:
                    history_call.edit(
                        history_call.source.replace("history.push", "navigate")
                    )

        # Convert lifecycle methods in hooks
        elif call.name == "componentDidMount":
            call.parent.edit("""
useEffect(() => {
    // Your componentDidMount logic here
}, []);
""")

Standardizing Props

Inferring Props from Usage

Add proper prop types and TypeScript interfaces based on how props are used:
# Add TypeScript interfaces for props
for function in codebase.functions:
    if not function.is_jsx:
        continue

    # Get props parameter
    props_param = function.parameters[0] if function.parameters else None
    if not props_param:
        continue

    # Collect used props
    used_props = set()
    for prop_access in function.function_calls:
        if f"{props_param.name}." in prop_access.source:
            prop_name = prop_access.source.split(".")[1]
            used_props.add(prop_name)

    # Create interface
    if used_props:
        interface_def = f"""
interface {function.name}Props {{
    {chr(10).join(f'    {prop}: any;' for prop in used_props)}
}}
"""
        function.insert_before(interface_def)
        # Update function signature
        function.edit(function.source.replace(
            f"({props_param.name})",
            f"({props_param.name}: {function.name}Props)"
        ))

Extracting Inline Props

Convert inline prop type definitions to separate type declarations:
# Iterate over all files in the codebase
for file in codebase.files:
    # Iterate over all functions in the file
    for function in file.functions:
        # Check if the function is a React functional component
        if function.is_jsx:  # Assuming is_jsx indicates a function component
            # Check if the function has inline props definition
            if len(function.parameters) == 1 and isinstance(function.parameters[0].type, Dict):
                # Extract the inline prop type
                inline_props: TSObjectType = function.parameters[0].type.source
                # Create a new type definition for the props
                props_type_name = f"{function.name}Props"
                props_type_definition = f"type {props_type_name} = {inline_props};"

                # Set the new type for the parameter
                function.parameters[0].set_type_annotation(props_type_name)
                # Add the new type definition to the file
                function.insert_before('\n' + props_type_definition + '\n')
This will convert components from:
function UserCard({ name, age }: { name: string; age: number }) {
  return (
    <div>
      {name} ({age})
    </div>
  );
}
To:
type UserCardProps = { name: string; age: number };

function UserCard({ name, age }: UserCardProps) {
  return (
    <div>
      {name} ({age})
    </div>
  );
}
Extracting prop types makes them reusable and easier to maintain. It also improves code readability by separating type definitions from component logic.

Updating Fragment Syntax

Modernize React Fragment syntax:
for function in codebase.functions:
    if not function.is_jsx:
        continue

    # Replace React.Fragment with <>
    for element in function.jsx_elements:
        if element.name == "React.Fragment":
            element.edit(element.source.replace(
                "<React.Fragment>",
                "<>"
            ).replace(
                "</React.Fragment>",
                "</>"
            ))

Organizing Components into Individual Files

A common modernization task is splitting files with multiple components into a more maintainable structure where each component has its own file. This is especially useful when modernizing legacy React codebases that might have grown organically.
# Initialize a dictionary to store files and their corresponding JSX components
files_with_jsx_components = {}

# Iterate through all files in the codebase
for file in codebase.files:
    # Check if the file is in the components directory
    if 'components' not in file.filepath:
        continue

    # Count the number of JSX components in the file
    jsx_count = sum(1 for function in file.functions if function.is_jsx)

    # Only proceed if there are multiple JSX components
    if jsx_count > 1:
        # Identify non-default exported components
        non_default_components = [
            func for func in file.functions
            if func.is_jsx and not func.is_exported
        ]
        default_components = [
            func for func in file.functions
            if func.is_jsx and func.is_exported and func.export.is_default_export()
        ]

        # Log the file path and its components
        print(f"📁 {file.filepath}:")
        for component in default_components:
            print(f"  🟢 {component.name} (default)")
        for component in non_default_components:
            print(f"  🔵 {component.name}")

            # Create a new directory path based on the original file's directory
            new_dir_path = "/".join(file.filepath.split("/")[:-1]) + "/" + file.name.split(".")[0]
            codebase.create_directory(new_dir_path, exist_ok=True)

            # Create a new file path for the component
            new_file_path = f"{new_dir_path}/{component.name}.tsx"
            new_file = codebase.create_file(new_file_path)

            # Log the movement of the component
            print(f"    🫸 Moved to: {new_file_path}")

            # Move the component to the new file
            component.move_to_file(new_file, strategy="add_back_edge")
This script will:
  1. Find files containing multiple React components
  2. Create a new directory structure based on the original file
  3. Move each non-default exported component to its own file
  4. Preserve imports and dependencies automatically
  5. Keep default exports in their original location
For example, given this structure:
components/
  Forms.tsx  # Contains Button, Input, Form (default)
It will create:
components/
  Forms.tsx  # Contains Form (default)
  forms/
    Button.tsx
    Input.tsx
The strategy="add_back_edge" parameter ensures that any components that were previously co-located can still import each other without circular dependencies. Learn more about moving code here.