Skip to main content

React Native Integration

GUN works with React Native, but requires additional setup for crypto polyfills and platform-specific storage adapters.

Installation

1

Install GUN and dependencies

Install GUN and required shims for React Native:
npm install gun
npm install @peculiar/webcrypto --save
npm install text-encoding --save
npm install buffer --save
2

Create shim file

Create a shim.js file in your project root to polyfill Node.js globals:
if (typeof __dirname === 'undefined') global.__dirname = '/'
if (typeof __filename === 'undefined') global.__filename = ''

if (typeof Buffer === 'undefined') global.Buffer = require('buffer').Buffer

const isDev = typeof __DEV__ === 'boolean' && __DEV__
process.env['NODE_ENV'] = isDev ? 'development' : 'production'
if (typeof localStorage !== 'undefined') {
  localStorage.debug = isDev ? '*' : ''
}

global.location = {
  protocol: 'file:',
  host: '',
};

const { TextEncoder, TextDecoder } = require('text-encoding');

global.TextDecoder = TextDecoder;
global.TextEncoder = TextEncoder;
3

Import shim in index.js

Import the shim at the very top of your index.js:
import './shim'
import { AppRegistry } from 'react-native'
import App from './App'
// ...
4

Set up crypto polyfill

Create a crypto polyfill component for WebCrypto API support.

WebCrypto Polyfill

GUN’s SEA (Security, Encryption, Authorization) requires the WebCrypto API. Here’s how to polyfill it:
import React, { Component } from 'react'
import { Crypto } from '@peculiar/webcrypto'

export default class PolyFillCrypto extends Component {
  constructor(props) {
    super(props)
    const crypto = new Crypto()
    global.crypto = crypto
    global.CryptoKey = crypto.CryptoKey
  }

  render() {
    return null
  }
}

AsyncStorage Adapter

For persistent storage in React Native, create an AsyncStorage adapter:
import { AsyncStorage } from 'react-native'

const adapter = {
  read: function(request, db) {
    const key = request.get['#']
    AsyncStorage.getItem(key).then(data => {
      if (data) {
        db.on('in', {'@': request['#'], put: JSON.parse(data), '#': key})
      }
    })
  },
  write: function(request, db) {
    const put = request.put
    Object.keys(put).forEach(key => {
      AsyncStorage.setItem(key, JSON.stringify(put[key]))
    })
  }
}

export default adapter

Complete Setup Example

Here’s a complete React Native app with GUN integration:

App.js

import React, {Component} from 'react'
import {View} from 'react-native'
import {Demo} from './Demo'
import PolyFillCrypto from './PolyFillCrypto'

export class App extends Component {
  render() {
    return (
      <View>
        <PolyFillCrypto />
        <Demo/>
      </View>
    )
  }
}

Demo Component with GUN

import * as React from 'react'
import {View, StyleSheet, TextInput, Text, TouchableOpacity, AsyncStorage} from 'react-native'
import Gun from 'gun/gun'
import 'gun/lib/open'
import '../extensions/sea'
import adapter from '../extensions/asyncStorageAdapter'

// Register AsyncStorage adapter
Gun.on('create', function(db) {
  this.to.next(db);
  const pluginInterop = function(middleware) {
    return function(request) {
      this.to.next(request);
      return middleware(request, db);
    };
  }

  db.on('get', pluginInterop(adapter.read));
  db.on('put', pluginInterop(adapter.write));
});

export class Demo extends React.Component {
    constructor() {
        super();

        this.gun = new Gun();
        this.user = this.gun.user();

        this.state = {
            authenticated: false,
            list: [],
            listText: '',
            username: '',
            password: '',
        }
    }

    hookUserList = () => {
        this.user.get('list').open((list) => {
            const userList = Object.keys(list).reduce((newList, key) => {
                if (!!Object.keys(list[key]).length) {
                    return [...newList, {text: list[key].text, key}];
                };
            }, []);
            this.setState({
                list: userList || [],
            });
        });
    }

    addToList = () => {
        this.user.get('list').set({text: this.state.listText});
    }

    doSignin = () => {
        this.user.auth(this.state.username, this.state.password, (d) => {
            if (d.err) {
                console.log('err', d.err);
                return;
            }

            this.setState({authenticated: true});
            this.hookUserList();
        });
    }

    doSignup = () => {
        this.user.create(this.state.username, this.state.password, () => {
            this.doSignin();
        });
    }

    render() {
        return (
            <View style={styles.container}>
                {this.state.authenticated ? (
                    <View style={styles.sub}>
                        {
                            !!this.state.list.length && 
                            this.state.list.map((item) => 
                                <Text key={item.key}>* {item.text}</Text>
                            )
                        }
                        <TextInput 
                            placeholder="text here" 
                            onChangeText={(listText) => this.setState({listText})} 
                            value={this.state.listText} 
                            style={styles.input} 
                        />
                        <TouchableOpacity onPress={this.addToList} style={styles.button}>
                            <Text>Add to list</Text>
                        </TouchableOpacity>
                    </View>
                ) : (
                    <View style={styles.sub}>
                        <TextInput 
                            placeholder="username" 
                            onChangeText={(username) => this.setState({username})} 
                            value={this.state.username} 
                            style={styles.input} 
                        />
                        <TextInput 
                            placeholder="password" 
                            secureTextEntry={true} 
                            onChangeText={(password) => this.setState({password})} 
                            value={this.state.password} 
                            style={styles.input} 
                        />
                        <TouchableOpacity onPress={this.doSignin} style={styles.button}>
                            <Text>Sign in</Text>
                        </TouchableOpacity>
                        <TouchableOpacity onPress={this.doSignup} style={styles.button}>
                            <Text>Sign up</Text>
                        </TouchableOpacity>
                    </View>
                )}
            </View>
        )
    }
}

const styles = StyleSheet.create({
    container: {
        justifyContent: 'center',
        alignItems: 'center',
        width: '100%',
        height: '100%',
    },
    sub: {
        height: '50%',
        width: '60%',
        justifyContent: 'space-between',
        padding: 4,
    },
    button: {
        backgroundColor: 'rgba(0, 0, 0, 0.3)',
        borderWidth: 1,
        borderRadius: 5,
        alignItems: 'center',
        justifyContent: 'center',
        width: '100%',
        height: 50,
    },
    input: {
        borderColor: 'black',
        borderWidth: 1,
        borderRadius: 5,
        width: '100%',
        height: 50,
        alignItems: 'center',
        justifyContent: 'center',
        paddingHorizontal: 16,
    },
});

Platform-Specific Considerations

iOS

  • WebCrypto polyfill is required for SEA functionality
  • AsyncStorage is deprecated - consider using @react-native-async-storage/async-storage

Android

  • Ensure network permissions are set in AndroidManifest.xml
  • Test with release builds as debug mode may have different performance characteristics

Common Issues

Buffer/Crypto Errors

Make sure shims are imported before any GUN imports:
import './shim' // Must be first!
import Gun from 'gun/gun'

Storage Not Persisting

Verify that the AsyncStorage adapter is registered before creating GUN instances.

Best Practices

  1. Load polyfills early: Import shims at the app entry point
  2. Use AsyncStorage adapter: For offline-first functionality
  3. Test on devices: Simulators may not accurately represent performance
  4. Handle permissions: Request necessary permissions for network access

Next Steps

Build docs developers (and LLMs) love