> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/preactjs/preact/llms.txt
> Use this file to discover all available pages before exploring further.

# React vs Preact Differences

> Key differences between React and Preact when using the compat layer

## Overview

While `preact/compat` provides excellent React compatibility, there are some differences in behavior and implementation details. Most applications won't encounter these, but it's important to be aware of them.

<Note>
  In most cases, preact/compat provides 100% compatibility with React for standard usage patterns. Differences typically only matter for edge cases or advanced usage.
</Note>

## Core Differences

### Synthetic Events

Preact uses native browser events instead of React's synthetic event system.

**React:**

```jsx theme={null}
function handleClick(e) {
  // e is a SyntheticEvent wrapper
  e.persist(); // Needed to access event async
  setTimeout(() => {
    console.log(e.type); // Works in React
  }, 100);
}
```

**Preact:**

```jsx theme={null}
function handleClick(e) {
  // e is a native Event
  // e.persist() is a no-op (see compat/src/render.js:100)
  setTimeout(() => {
    console.log(e.type); // Works in Preact
  }, 100);
}
```

<Note>
  The compat layer adds a no-op `persist()` method to events at [compat/src/render.js:100](https://github.com/preactjs/preact/blob/main/compat/src/render.js:100) for compatibility, but it's not needed since Preact uses native events.
</Note>

**Implications:**

* Native events are not pooled (no need for `e.persist()`)
* Event properties can be accessed asynchronously without issues
* Slightly better performance due to no synthetic event overhead

### Event Naming

Preact automatically normalizes React event names to their browser equivalents:

```jsx theme={null}
// These are automatically converted by the compat layer:
<input onChange={handler} />     // → oninput (for text inputs)
<input onDoubleClick={handler} /> // → ondblclick
<input onFocus={handler} />       // → onfocusin  
<input onBlur={handler} />        // → onfocusout
```

The conversion logic is in [compat/src/render.js:168](https://github.com/preactjs/preact/blob/main/compat/src/render.js:168).

<Warning>
  For `<input type="file">`, `<input type="checkbox">`, and `<input type="radio">`, `onChange` behaves exactly like React's onChange, not as oninput.
</Warning>

### className vs class

Preact supports both `class` and `className` props:

```jsx theme={null}
// Both work in Preact:
<div className="foo" />
<div class="foo" />
```

The compat layer ensures both are supported (see [compat/src/render.js:227](https://github.com/preactjs/preact/blob/main/compat/src/render.js:227)).

## Lifecycle Methods

### Unsafe Lifecycle Methods

Preact supports both prefixed and unprefixed unsafe lifecycle methods through property getters/setters:

```jsx theme={null}
class MyComponent extends Component {
  // Both of these work:
  componentWillMount() { }
  UNSAFE_componentWillMount() { }
  
  componentWillReceiveProps(nextProps) { }
  UNSAFE_componentWillReceiveProps(nextProps) { }
  
  componentWillUpdate(nextProps, nextState) { }
  UNSAFE_componentWillUpdate(nextProps, nextState) { }
}
```

The aliasing is implemented at [compat/src/render.js:49](https://github.com/preactjs/preact/blob/main/compat/src/render.js:49).

<Note>
  While these lifecycle methods are supported, they're deprecated in React and should be avoided in new code.
</Note>

## Concurrent Features

### Transitions and Deferred Values

`useTransition` and `useDeferredValue` are implemented but don't provide true concurrent rendering:

```jsx theme={null}
import { useTransition, useDeferredValue } from 'react';

function MyComponent() {
  const [isPending, startTransition] = useTransition();
  // isPending is always false
  // startTransition executes immediately
  
  const deferredValue = useDeferredValue(value);
  // Returns the value immediately (no deferral)
}
```

Implementation details:

* `startTransition` is a passthrough function at [compat/src/hooks.js:52](https://github.com/preactjs/preact/blob/main/compat/src/hooks.js:52)
* `useDeferredValue` returns the value unchanged at [compat/src/hooks.js:56](https://github.com/preactjs/preact/blob/main/compat/src/hooks.js:56)
* `useTransition` always returns `[false, startTransition]` at [compat/src/hooks.js:60](https://github.com/preactjs/preact/blob/main/compat/src/hooks.js:60)

<Warning>
  Code that relies on actual concurrent rendering behavior won't work the same way in Preact. These hooks are provided for API compatibility only.
</Warning>

### flushSync

`flushSync` is implemented as a passthrough in Preact:

```jsx theme={null}
import { flushSync } from 'react-dom';

// In React: Forces synchronous render
// In Preact: Just executes the callback (compat/src/index.js:132)
flushSync(() => {
  setState(newValue);
});
```

### Suspense and Lazy Loading

Suspense and lazy loading are fully supported:

```jsx theme={null}
import { lazy, Suspense } from 'react';

const LazyComponent = lazy(() => import('./Component'));

function App() {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <LazyComponent />
    </Suspense>
  );
}
```

The implementation at [compat/src/suspense.js:218](https://github.com/preactjs/preact/blob/main/compat/src/suspense.js:218) provides full Suspense support including:

* Error boundaries for promise rejection
* Proper fallback rendering
* Support for nested Suspense boundaries

## Rendering Differences

### Render Return Value

Preact's render returns the component instance, matching React's behavior:

```jsx theme={null}
import { render } from 'react-dom';

const instance = render(<App />, container);
// instance is the root component (or null for functional components)
```

This is ensured by the compat layer at [compat/src/render.js:86](https://github.com/preactjs/preact/blob/main/compat/src/render.js:86).

### Container Clearing

React clears container content on first render:

```jsx theme={null}
const container = document.getElementById('root');
container.innerHTML = '<div>This will be cleared</div>';

// First render clears the container
render(<App />, container);
```

Preact/compat implements the same behavior at [compat/src/render.js:79](https://github.com/preactjs/preact/blob/main/compat/src/render.js:79).

### Hydration

Hydration works similarly to React:

```jsx theme={null}
import { hydrateRoot } from 'react-dom/client';
// or legacy:
import { hydrate } from 'react-dom';

hydrate(<App />, container);
```

## Props and Attributes

### defaultValue and value

The compat layer handles `defaultValue` as a fallback for `value`:

```jsx theme={null}
// If value is null/undefined, defaultValue is used
<input value={null} defaultValue="fallback" />
// Renders with value="fallback"
```

Implementation: [compat/src/render.js:152](https://github.com/preactjs/preact/blob/main/compat/src/render.js:152)

### SVG Attributes

Preact automatically converts camelCase SVG attributes to kebab-case:

```jsx theme={null}
// These are converted automatically:
<svg>
  <text textAnchor="middle" />      {/* → text-anchor */}
  <path strokeWidth={2} />           {/* → stroke-width */}
  <circle fillOpacity={0.5} />       {/* → fill-opacity */}
</svg>
```

The conversion regex is at [compat/src/render.js:32](https://github.com/preactjs/preact/blob/main/compat/src/render.js:32).

### Style Object

Numeric style values automatically get 'px' appended (except for unitless properties):

```jsx theme={null}
<div style={{ 
  width: 100,        // → "100px"
  height: 200,       // → "200px"
  flex: 1,           // → "1" (unitless)
  opacity: 0.5,      // → "0.5" (unitless)
  zIndex: 10         // → "10" (unitless)
}} />
```

Implementation: [compat/src/render.js:148](https://github.com/preactjs/preact/blob/main/compat/src/render.js:148)

### Boolean Attributes

The download attribute with `true` value becomes an empty string:

```jsx theme={null}
// In React and Preact/compat:
<a href="/file" download={true} />
// Renders as: <a href="/file" download="">
```

This prevents the file from being named "true" ([compat/src/render.js:159](https://github.com/preactjs/preact/blob/main/compat/src/render.js:159)).

## Component Features

### PureComponent

PureComponent performs shallow comparison of props and state:

```jsx theme={null}
import { PureComponent } from 'react';

class MyComponent extends PureComponent {
  render() {
    // Only re-renders if props or state shallowly differ
    return <div>{this.props.value}</div>;
  }
}
```

The shallow comparison implementation is at [compat/src/PureComponent.js:14](https://github.com/preactjs/preact/blob/main/compat/src/PureComponent.js:14) and uses the `shallowDiffers` utility from [compat/src/util.js:9](https://github.com/preactjs/preact/blob/main/compat/src/util.js:9).

### memo()

The `memo` higher-order component works identically to React:

```jsx theme={null}
import { memo } from 'react';

const MemoizedComponent = memo(MyComponent, (prevProps, nextProps) => {
  // Return true if props are equal (skip render)
  return prevProps.id === nextProps.id;
});
```

Implementation: [compat/src/memo.js:11](https://github.com/preactjs/preact/blob/main/compat/src/memo.js:11)

### forwardRef()

`forwardRef` is fully supported:

```jsx theme={null}
import { forwardRef } from 'react';

const MyInput = forwardRef((props, ref) => {
  return <input ref={ref} {...props} />;
});
```

The implementation at [compat/src/forwardRef.js:12](https://github.com/preactjs/preact/blob/main/compat/src/forwardRef.js:12) ensures compatibility with libraries like mobx-react by:

* Adding the `$$typeof` symbol
* Exposing a `render` property
* Setting `isReactComponent` flag

## Children API

The Children API is fully compatible:

```jsx theme={null}
import { Children } from 'react';

function Parent({ children }) {
  const count = Children.count(children);
  const array = Children.toArray(children);
  
  Children.forEach(children, (child, index) => {
    console.log(child, index);
  });
  
  const mapped = Children.map(children, (child) => {
    return cloneElement(child, { key: child.key });
  });
  
  const only = Children.only(children); // Throws if not exactly one child
  
  return <div>{children}</div>;
}
```

Implementation: [compat/src/Children.js:9](https://github.com/preactjs/preact/blob/main/compat/src/Children.js:9)

## StrictMode

StrictMode is a no-op in Preact (it's aliased to Fragment):

```jsx theme={null}
import { StrictMode } from 'react';

// In Preact, this just renders children without any checks
<StrictMode>
  <App />
</StrictMode>
```

See [compat/src/index.js:151](https://github.com/preactjs/preact/blob/main/compat/src/index.js:151).

<Warning>
  StrictMode development warnings and double-rendering don't occur in Preact.
</Warning>

## Utilities

### isValidElement

Checks if a value is a valid Preact/React element:

```jsx theme={null}
import { isValidElement } from 'react';

const element = <div />;
isValidElement(element); // true
isValidElement('string'); // false
isValidElement(null); // false
```

Implementation checks for the `$$typeof` property at [compat/src/index.js:58](https://github.com/preactjs/preact/blob/main/compat/src/index.js:58).

### findDOMNode

Legacy API that's supported but deprecated:

```jsx theme={null}
import { findDOMNode } from 'react-dom';

class MyComponent extends Component {
  componentDidMount() {
    const node = findDOMNode(this);
    // node is the DOM element
  }
}
```

<Warning>
  findDOMNode is deprecated in React and may not work with all component types. Use refs instead.
</Warning>

### unmountComponentAtNode

```jsx theme={null}
import { unmountComponentAtNode } from 'react-dom';

const container = document.getElementById('root');
render(<App />, container);

// Later:
const didUnmount = unmountComponentAtNode(container);
// didUnmount is true if a component was unmounted
```

Implementation: [compat/src/index.js:101](https://github.com/preactjs/preact/blob/main/compat/src/index.js:101)

## Hooks Differences

### useInsertionEffect

`useInsertionEffect` is aliased to `useLayoutEffect` in Preact:

```jsx theme={null}
import { useInsertionEffect } from 'react';

// In Preact, this behaves like useLayoutEffect
useInsertionEffect(() => {
  // Insert styles, etc.
}, []);
```

See [compat/src/hooks.js:66](https://github.com/preactjs/preact/blob/main/compat/src/hooks.js:66).

### useSyncExternalStore

`useSyncExternalStore` is fully implemented:

```jsx theme={null}
import { useSyncExternalStore } from 'react';

function useStore(store) {
  return useSyncExternalStore(
    store.subscribe,
    store.getSnapshot
  );
}
```

The implementation at [compat/src/hooks.js:8](https://github.com/preactjs/preact/blob/main/compat/src/hooks.js:8) provides React-compatible behavior for external store subscriptions.

## Server-Side Rendering

### renderToString

Server rendering is fully supported:

```jsx theme={null}
import { renderToString } from 'react-dom/server';

const html = renderToString(<App />);
```

Preact's SSR implementation is available through the compat layer.

## Known Limitations

<Warning>
  The following React features are not fully supported in Preact:
</Warning>

### 1. Concurrent Rendering

Preact doesn't implement React's concurrent rendering features:

* Time slicing
* Selective hydration
* Priority-based rendering

Transition hooks are no-ops (see Concurrent Features section above).

### 2. Server Components

React Server Components (RSC) are not supported:

```jsx theme={null}
// Not supported in Preact:
'use server';
'use client';
```

### 3. Error Boundaries in Render

Some edge cases with error boundaries may differ from React.

### 4. Deep React Internals

Libraries that access React internals via `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED` may not work:

```jsx theme={null}
// Preact provides a partial implementation at compat/src/render.js:303
import { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } from 'react';

// Some internals are provided for library compatibility,
// but not all React internals are available
```

### 5. Legacy Context API

The legacy context API (getChildContext) has limited support compared to the modern Context API.

## Performance Differences

### Bundle Size

Preact with compat is significantly smaller than React:

* **Preact core**: \~3kb gzipped
* **Preact + compat**: \~5-6kb gzipped
* **React + ReactDOM**: \~40kb+ gzipped

### Runtime Performance

Preact is generally faster than React due to:

* No synthetic event system overhead
* Simpler reconciliation algorithm
* Smaller runtime means better parsing time
* Direct DOM manipulation

### Compatibility Overhead

The compat layer adds:

* \~2-3kb to bundle size
* Minimal runtime overhead for prop normalization
* Event handling normalization

<Note>
  If you don't need React compatibility, importing from `preact` and `preact/hooks` directly instead of using the compat layer will give you the smallest possible bundle.
</Note>

## Best Practices

1. **Use the compat layer for third-party libraries only**: Import from `preact` directly in your own code when possible.

2. **Test thoroughly**: While most code works identically, test your app's critical paths.

3. **Check library compatibility**: Before adopting a React library, verify it works with Preact (most do).

4. **Avoid React internals**: Don't rely on undocumented React internals.

5. **Use refs over findDOMNode**: Modern ref patterns are better supported.

6. **Monitor bundle size**: Use tools like webpack-bundle-analyzer to track your bundle.

## When to Use Preact

Preact with compat is ideal when:

* Bundle size is critical (mobile, slow networks)
* You want React's API with better performance
* You're building a new project and want React library compatibility
* You're migrating from React and want minimal code changes

Consider staying with React if:

* You need concurrent rendering features
* You're using React Server Components
* You have deep integrations with React internals
* Your team is heavily invested in React-specific tooling

## Next Steps

<CardGroup cols={2}>
  <Card title="Compat Overview" icon="book" href="/compat/overview">
    Review all supported React features
  </Card>

  <Card title="Migration Guide" icon="right-left" href="/compat/migration-from-react">
    Migrate your React app to Preact
  </Card>
</CardGroup>
