New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a React/Redux example #141

Open
alazier opened this Issue Nov 16, 2015 · 35 comments

Comments

Projects
None yet
@alazier
Contributor

alazier commented Nov 16, 2015

No description provided.

@jwhitley

This comment has been minimized.

Show comment
Hide comment
@jwhitley

jwhitley Mar 8, 2016

+1 I'm really quite curious as to what the envisioned integration between Realm and Redux would be.

As each of Redux and Realm are presented, they seem to be at odds. On one hand, Redux depends on two things:

  1. The entire app state is represented in a single state tree.
  2. The state tree is mutated by a (possibly hierarchical) pure reducer function of form
    (oldState, action) => newState

On the other, Realm seems to be designed around the principle that components access Realm data via persistent results, effectively data views, which are auto-updated. A naive merging of a Redux-based approach and the React example code in this repo, would turn Realm into a second repository of application state which muddies the entire story around Redux and its related tools.

Since application state would no longer be fully represented in Redux, it's unclear how Redux patterns like redux-saga, redux-undo, could work properly anymore. Effectively, reducers can't even touch persistent data state in Realm, since they need to be pure functions.

One could resolve this conflict by treating Realm like any other store, pushed to the effect-ful edge of a React/Redux app, e.g. where an action creator or saga marshals effects, dispatches actions, and otherwise handles workflow in a Redux-friendly manner.

That raises the following question: is there some better, more elegant, integration of Realm into a Redux app?

jwhitley commented Mar 8, 2016

+1 I'm really quite curious as to what the envisioned integration between Realm and Redux would be.

As each of Redux and Realm are presented, they seem to be at odds. On one hand, Redux depends on two things:

  1. The entire app state is represented in a single state tree.
  2. The state tree is mutated by a (possibly hierarchical) pure reducer function of form
    (oldState, action) => newState

On the other, Realm seems to be designed around the principle that components access Realm data via persistent results, effectively data views, which are auto-updated. A naive merging of a Redux-based approach and the React example code in this repo, would turn Realm into a second repository of application state which muddies the entire story around Redux and its related tools.

Since application state would no longer be fully represented in Redux, it's unclear how Redux patterns like redux-saga, redux-undo, could work properly anymore. Effectively, reducers can't even touch persistent data state in Realm, since they need to be pure functions.

One could resolve this conflict by treating Realm like any other store, pushed to the effect-ful edge of a React/Redux app, e.g. where an action creator or saga marshals effects, dispatches actions, and otherwise handles workflow in a Redux-friendly manner.

That raises the following question: is there some better, more elegant, integration of Realm into a Redux app?

@appden

This comment has been minimized.

Show comment
Hide comment
@appden

appden Mar 9, 2016

Contributor

@jwhitley That was a very thorough and accurate overview of the challenges of integrating Realm and Redux. It's actually something we have been actively working towards resolving by allowing for deep snapshots of data inside a Realm. At its core, Realm supports this because of its nature as a write-only database, but there are some challenges we have yet to overcome to properly expose that functionality in a language binding. At the moment, when integrating with Redux, it's probably best to treat Realm purely as a persistence layer, separate from the application state. Once we have support for immutable snapshots, we will create an example app that leverages that functionality to integrate with Redux.

Contributor

appden commented Mar 9, 2016

@jwhitley That was a very thorough and accurate overview of the challenges of integrating Realm and Redux. It's actually something we have been actively working towards resolving by allowing for deep snapshots of data inside a Realm. At its core, Realm supports this because of its nature as a write-only database, but there are some challenges we have yet to overcome to properly expose that functionality in a language binding. At the moment, when integrating with Redux, it's probably best to treat Realm purely as a persistence layer, separate from the application state. Once we have support for immutable snapshots, we will create an example app that leverages that functionality to integrate with Redux.

@robwalkerco

This comment has been minimized.

Show comment
Hide comment
@robwalkerco

robwalkerco commented Mar 10, 2016

+1

@jwhitley

This comment has been minimized.

Show comment
Hide comment
@jwhitley

jwhitley Mar 11, 2016

@appden I'm currently implementing the above approach in my app, and have run into an error from Realm: uncaught Error: Value not convertible to a number.

I need a primary key so that I can handle edits of existing objects (i.e. DB updates), via the following usage of create:

realm.write(() =>  {
  realm.create('Thing', { id: 123, foo: "...", bar: "..." }, true);
})

However, upon the initial creation of an object, I don't have an id property yet, and realm.create() emits the Value not convertible to a number error. This error occurs whether or not I pass true or false for the third argument at creation time. Presumably this is because create() isn't finding a numerical value for id in the properties of the passed object. If so, the messaging could be vastly improved.

Does Realm require that the app explicitly manage creating unique PKs if they're enabled in a model's schema? If so, that should really be added to the docs. Pushing that logic, traditionally managed by the DB, out onto the app is a non-trivial burden.

jwhitley commented Mar 11, 2016

@appden I'm currently implementing the above approach in my app, and have run into an error from Realm: uncaught Error: Value not convertible to a number.

I need a primary key so that I can handle edits of existing objects (i.e. DB updates), via the following usage of create:

realm.write(() =>  {
  realm.create('Thing', { id: 123, foo: "...", bar: "..." }, true);
})

However, upon the initial creation of an object, I don't have an id property yet, and realm.create() emits the Value not convertible to a number error. This error occurs whether or not I pass true or false for the third argument at creation time. Presumably this is because create() isn't finding a numerical value for id in the properties of the passed object. If so, the messaging could be vastly improved.

Does Realm require that the app explicitly manage creating unique PKs if they're enabled in a model's schema? If so, that should really be added to the docs. Pushing that logic, traditionally managed by the DB, out onto the app is a non-trivial burden.

@alazier

This comment has been minimized.

Show comment
Hide comment
@alazier

alazier Mar 11, 2016

Contributor

Primary key values always need to be provided when calling the create method. It is currently up to the user to manage primary keys but there are plans to add auto-incrementing primary keys sometime in the future. In the shorter term we may be able to support functions for default values which would allow users to concisely build their own auto-incrementing functionality.

For the time being we will update the documentation with the current limitations.

Contributor

alazier commented Mar 11, 2016

Primary key values always need to be provided when calling the create method. It is currently up to the user to manage primary keys but there are plans to add auto-incrementing primary keys sometime in the future. In the shorter term we may be able to support functions for default values which would allow users to concisely build their own auto-incrementing functionality.

For the time being we will update the documentation with the current limitations.

@fungilation

This comment has been minimized.

Show comment
Hide comment
@fungilation

fungilation Mar 23, 2016

A corollary, for me just starting with RN. For managing UI states in a brand new RN app, is there benefit to use Redux at all, if/could/should I use Realm entirely for managing UI states? Persistence is what Realm uniquely brings to UI states, so I'm wondering for starting out, should I rely on it for managing UI states entirely or I should still use Redux in conjunction.

Thanks for your thoughts on this.

fungilation commented Mar 23, 2016

A corollary, for me just starting with RN. For managing UI states in a brand new RN app, is there benefit to use Redux at all, if/could/should I use Realm entirely for managing UI states? Persistence is what Realm uniquely brings to UI states, so I'm wondering for starting out, should I rely on it for managing UI states entirely or I should still use Redux in conjunction.

Thanks for your thoughts on this.

@jwhitley

This comment has been minimized.

Show comment
Hide comment
@jwhitley

jwhitley Mar 23, 2016

@fungilation Good question. Here's my take as a Realm user who has been integrating it into a Redux-based app.

tl;dr: I trade off some Realm features in favor of Redux features. Read on for the details.

Realm's current approach is essentially to turn queries into live view objects (Realm.Results) which drive your UI. The live updates mean that any writes that occur are immediately manifested in existing result sets. However, that side-effect based workflow doesn't provide React with knowledge that anything changed. You can see this in the realm-js example code. Note the call to this.forceUpdate() in that method. In the React world, calling forceUpdate() is a code smell:

By default, when your component's state or props change, your component will re-render. However, if these change implicitly (eg: data deep within an object changes without changing the object itself) or if your render() method depends on some other data, you can tell React that it needs to re-run render() by calling forceUpdate().
[...]
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().

Realm also has change events, but those are currently a blunt hammer. You register for literally anything that changes, and are given no information other than "something changed". Your only recourse is therefore to re-render the universe.

So on that point, there's some architectural friction between React and Realm right out of the gate. However, realm-js is still quite young, and there are signs that this situation will improve greatly over time.

As for Redux, if you haven't already, definitely give time to Dan Abramov's excellent series of short videos. That introduces not only the basics of Redux, but it helps greatly to understand the motivations behind why it works the way it does. Speaking personally, here are some things I really like about a Redux-based app architecture:

  1. Actions in Redux are data rather than functions or methods. This opens up opportunities for logging, middleware, history manipulation, and more.
  2. Following on no. 1, employing redux-saga means the workflows that respond to your actions are also (mostly) data. A saga generator function can be thought of as a function that returns a description of what to do when an action is received. redux-saga is then an engine that reads these descriptions and executes your workflow. That's what makes sagas highly testable. I also love that sagas collect logic that is often scattered around in traditional controllers, delegate methods, etc.. Saga unit tests would often end up being integration tests under other frameworks to cover the same functionality
  3. Even without going down the redux-saga route, the action creator pattern keeps a nice separation between side-effects (in the action creator functions) and the actions themselves (pure data structures).
  4. I think we're going to see a lot more sophistication around the power of Redux-style immutable state and first-class actions over time. A lot of interesting apps eventually have to fight with remote synchronization issues. I've been contemplating interesting possibilities around connecting a subset of actions and an Operational Transformation layer, for example.

As you can tell, I'm currently a fan of Redux-based apps. Which means that I use Realm just as I described above: like it's a traditional local (or even remote) store. When I query Realm, e.g. realm.objects('Thing'), I must make a deep copy of the returned results to put in the Redux store. Redux fundamentally assumes an immutable store. If you squint a bit, this is little different than getting some result-set data structure from, e.g. the SQLite library, and mapping that into plain Javascript objects to add to the store.

jwhitley commented Mar 23, 2016

@fungilation Good question. Here's my take as a Realm user who has been integrating it into a Redux-based app.

tl;dr: I trade off some Realm features in favor of Redux features. Read on for the details.

Realm's current approach is essentially to turn queries into live view objects (Realm.Results) which drive your UI. The live updates mean that any writes that occur are immediately manifested in existing result sets. However, that side-effect based workflow doesn't provide React with knowledge that anything changed. You can see this in the realm-js example code. Note the call to this.forceUpdate() in that method. In the React world, calling forceUpdate() is a code smell:

By default, when your component's state or props change, your component will re-render. However, if these change implicitly (eg: data deep within an object changes without changing the object itself) or if your render() method depends on some other data, you can tell React that it needs to re-run render() by calling forceUpdate().
[...]
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render().

Realm also has change events, but those are currently a blunt hammer. You register for literally anything that changes, and are given no information other than "something changed". Your only recourse is therefore to re-render the universe.

So on that point, there's some architectural friction between React and Realm right out of the gate. However, realm-js is still quite young, and there are signs that this situation will improve greatly over time.

As for Redux, if you haven't already, definitely give time to Dan Abramov's excellent series of short videos. That introduces not only the basics of Redux, but it helps greatly to understand the motivations behind why it works the way it does. Speaking personally, here are some things I really like about a Redux-based app architecture:

  1. Actions in Redux are data rather than functions or methods. This opens up opportunities for logging, middleware, history manipulation, and more.
  2. Following on no. 1, employing redux-saga means the workflows that respond to your actions are also (mostly) data. A saga generator function can be thought of as a function that returns a description of what to do when an action is received. redux-saga is then an engine that reads these descriptions and executes your workflow. That's what makes sagas highly testable. I also love that sagas collect logic that is often scattered around in traditional controllers, delegate methods, etc.. Saga unit tests would often end up being integration tests under other frameworks to cover the same functionality
  3. Even without going down the redux-saga route, the action creator pattern keeps a nice separation between side-effects (in the action creator functions) and the actions themselves (pure data structures).
  4. I think we're going to see a lot more sophistication around the power of Redux-style immutable state and first-class actions over time. A lot of interesting apps eventually have to fight with remote synchronization issues. I've been contemplating interesting possibilities around connecting a subset of actions and an Operational Transformation layer, for example.

As you can tell, I'm currently a fan of Redux-based apps. Which means that I use Realm just as I described above: like it's a traditional local (or even remote) store. When I query Realm, e.g. realm.objects('Thing'), I must make a deep copy of the returned results to put in the Redux store. Redux fundamentally assumes an immutable store. If you squint a bit, this is little different than getting some result-set data structure from, e.g. the SQLite library, and mapping that into plain Javascript objects to add to the store.

@jwhitley

This comment has been minimized.

Show comment
Hide comment
@jwhitley

jwhitley Mar 23, 2016

Following on @alazier's earlier comment, I ended up creating this Sequence object store in my top-level realm schema file. This is a hand-rolled implementation of autoincrementing sequences for realm-js. The Sequence table has keys which are the names of other objects in the schema. The value for each key is the highest used id primary key for each object type.

import Realm from 'realm'

const SCHEMA_VERSION = 0

class Thing {
}
Thing.schema = {
  name: 'Thing',
  primaryKey: 'id',
  properties: {
    id:    'int',
    thingProp1: 'string',
    thingProp2:  'string',
  }
}

class Sequence {
  static save(schema, props) {
    let saved;

    realm.write(() => {
      let obj = {...props};

      if (obj.id === undefined) {
        let seq = realm.objects('Sequence').filtered(`name = "${schema}"`)[0];
        if (seq === undefined) {
          seq = realm.create('Sequence', { name: schema, value: 0 });
        }
        obj.id = seq.next();
      }
      saved = realm.create(schema, obj, true);
    })

    return {...saved};
  }

  next() {
    this.value = this.value+1;
    return this.value;
  }
}

Sequence.schema = {
  name: 'Sequence',
  primaryKey: 'name',
  properties: {
    name:  'string',
    value: 'int',
  }
}

const realm = new Realm({
  schema: [Thing, Sequence],
  schemaVersion: SCHEMA_VERSION
})

export { realm, Sequence };

Then in an action creator (or saga, in my case), saving a Thing might look like this:

function saveThing(athing) {
  // athing is just a plain Javascript object with properties to be persisted.
  // it may or may not have an 'id' property.  If it does, update the existing object 
  // in the Realm store with the matching id.  If not, it's a new object.  Assign it a
  // unique 'id' and save it.
  let saved = Sequence.save('Thing', athing);
  return { type: 'SAVE_THING', payload: saved };
}

jwhitley commented Mar 23, 2016

Following on @alazier's earlier comment, I ended up creating this Sequence object store in my top-level realm schema file. This is a hand-rolled implementation of autoincrementing sequences for realm-js. The Sequence table has keys which are the names of other objects in the schema. The value for each key is the highest used id primary key for each object type.

import Realm from 'realm'

const SCHEMA_VERSION = 0

class Thing {
}
Thing.schema = {
  name: 'Thing',
  primaryKey: 'id',
  properties: {
    id:    'int',
    thingProp1: 'string',
    thingProp2:  'string',
  }
}

class Sequence {
  static save(schema, props) {
    let saved;

    realm.write(() => {
      let obj = {...props};

      if (obj.id === undefined) {
        let seq = realm.objects('Sequence').filtered(`name = "${schema}"`)[0];
        if (seq === undefined) {
          seq = realm.create('Sequence', { name: schema, value: 0 });
        }
        obj.id = seq.next();
      }
      saved = realm.create(schema, obj, true);
    })

    return {...saved};
  }

  next() {
    this.value = this.value+1;
    return this.value;
  }
}

Sequence.schema = {
  name: 'Sequence',
  primaryKey: 'name',
  properties: {
    name:  'string',
    value: 'int',
  }
}

const realm = new Realm({
  schema: [Thing, Sequence],
  schemaVersion: SCHEMA_VERSION
})

export { realm, Sequence };

Then in an action creator (or saga, in my case), saving a Thing might look like this:

function saveThing(athing) {
  // athing is just a plain Javascript object with properties to be persisted.
  // it may or may not have an 'id' property.  If it does, update the existing object 
  // in the Realm store with the matching id.  If not, it's a new object.  Assign it a
  // unique 'id' and save it.
  let saved = Sequence.save('Thing', athing);
  return { type: 'SAVE_THING', payload: saved };
}
@fungilation

This comment has been minimized.

Show comment
Hide comment
@fungilation

fungilation Mar 24, 2016

@jwhitley, thanks for the thorough explanation! Your experience appreciated. I come from Meteor and Realm's reactive data objects is quite familiar and "convenient". Redux's functional and pure approach is harder to wrap my head around but I am already going through Dan Abramov's videos. Looks like using it with React Native is still the way to go, with Realm used to populate initial state, and save to Realm at save points.

Thanks for the Sequence autoincrement class too.

fungilation commented Mar 24, 2016

@jwhitley, thanks for the thorough explanation! Your experience appreciated. I come from Meteor and Realm's reactive data objects is quite familiar and "convenient". Redux's functional and pure approach is harder to wrap my head around but I am already going through Dan Abramov's videos. Looks like using it with React Native is still the way to go, with Realm used to populate initial state, and save to Realm at save points.

Thanks for the Sequence autoincrement class too.

@MichaelDanielTom

This comment has been minimized.

Show comment
Hide comment
@MichaelDanielTom

MichaelDanielTom Apr 30, 2016

Hey guys, what's your opinion on saving Realm objects into the Redux store vs saving some type of primary key - Realm object type tuple? It seems that in terms of keeping only the bare minimum amount of data in Realm, it would make sense to only save the key, and then get the entire object through a selector. However for actually binding the redux state to the UI, saving the entire object seems a ton easier.

P.S. Thanks for articulating the tradeoffs and options @jwhitley, that was helpful 👍

MichaelDanielTom commented Apr 30, 2016

Hey guys, what's your opinion on saving Realm objects into the Redux store vs saving some type of primary key - Realm object type tuple? It seems that in terms of keeping only the bare minimum amount of data in Realm, it would make sense to only save the key, and then get the entire object through a selector. However for actually binding the redux state to the UI, saving the entire object seems a ton easier.

P.S. Thanks for articulating the tradeoffs and options @jwhitley, that was helpful 👍

@fungilation

This comment has been minimized.

Show comment
Hide comment
@fungilation

fungilation Apr 30, 2016

On a tangent, what about integration with Redux Persist? With its autoRehydrate() and persistStore(), it'd work quite automagically as a drop in for persistence. It currently uses AsyncStorage as storage, is Realm a natural fit here instead?

fungilation commented Apr 30, 2016

On a tangent, what about integration with Redux Persist? With its autoRehydrate() and persistStore(), it'd work quite automagically as a drop in for persistence. It currently uses AsyncStorage as storage, is Realm a natural fit here instead?

@MichaelDanielTom

This comment has been minimized.

Show comment
Hide comment
@MichaelDanielTom

MichaelDanielTom Apr 30, 2016

@fungilation Are you suggesting that all realm integration happens during the persist step, and that UI updates are completely handled by Redux? I feel like simply swapping out AsyncStorage for Realm wouldn't do that much if it's only used as a key-value store.

MichaelDanielTom commented Apr 30, 2016

@fungilation Are you suggesting that all realm integration happens during the persist step, and that UI updates are completely handled by Redux? I feel like simply swapping out AsyncStorage for Realm wouldn't do that much if it's only used as a key-value store.

@fungilation

This comment has been minimized.

Show comment
Hide comment
@fungilation

fungilation Apr 30, 2016

It does as its drop in for initial integration, while the data can be
queried in other ways for new alternate views independent of redux? I
haven't thought this out fully so consider this a proposal and discuss on
whether such integration is worthwhile to the community.
On Sat, Apr 30, 2016 at 11:33 AM Michael Tom notifications@github.com
wrote:

@fungilation https://github.com/fungilation Are you suggesting that all
realm integration happens during the persist step, and that UI updates are
completely handled by Redux? I feel like simply swapping out AsyncStorage
for Realm wouldn't do that much if it's only used as a key-value store.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#141 (comment)

fungilation commented Apr 30, 2016

It does as its drop in for initial integration, while the data can be
queried in other ways for new alternate views independent of redux? I
haven't thought this out fully so consider this a proposal and discuss on
whether such integration is worthwhile to the community.
On Sat, Apr 30, 2016 at 11:33 AM Michael Tom notifications@github.com
wrote:

@fungilation https://github.com/fungilation Are you suggesting that all
realm integration happens during the persist step, and that UI updates are
completely handled by Redux? I feel like simply swapping out AsyncStorage
for Realm wouldn't do that much if it's only used as a key-value store.


You are receiving this because you were mentioned.
Reply to this email directly or view it on GitHub
#141 (comment)

@MichaelDanielTom

This comment has been minimized.

Show comment
Hide comment
@MichaelDanielTom

MichaelDanielTom May 8, 2016

@jwhitley Could you provide an example of how exactly you use Realm as the persistent store, if you're making deep copies into Redux? Are all realm actions defined in sagas, with actions like refreshUser, which makes a network request, saves it to realm, and saves the resulting realm object into the Redux state? How are you handling normalization handled within the redux state?

Sorry for all the questions, but I'm trying to decide whether it would be easier to just use normalizr and Redux and get rid of the Realm layer, or if there will be significant performance benefits in the future from using Realm. Thanks!

MichaelDanielTom commented May 8, 2016

@jwhitley Could you provide an example of how exactly you use Realm as the persistent store, if you're making deep copies into Redux? Are all realm actions defined in sagas, with actions like refreshUser, which makes a network request, saves it to realm, and saves the resulting realm object into the Redux state? How are you handling normalization handled within the redux state?

Sorry for all the questions, but I'm trying to decide whether it would be easier to just use normalizr and Redux and get rid of the Realm layer, or if there will be significant performance benefits in the future from using Realm. Thanks!

@EngineerDiab

This comment has been minimized.

Show comment
Hide comment
@EngineerDiab

EngineerDiab Jun 9, 2016

How can deep copies be made in realm? I thought only shallow copies were supported to date?

EngineerDiab commented Jun 9, 2016

How can deep copies be made in realm? I thought only shallow copies were supported to date?

@alazier

This comment has been minimized.

Show comment
Hide comment
@alazier

alazier Jun 9, 2016

Contributor

@EngineerDiab at the moment only shallow copies are possible unless you copy data out of the realm. Deep copies is something we are working towards in the longer term though.

Contributor

alazier commented Jun 9, 2016

@EngineerDiab at the moment only shallow copies are possible unless you copy data out of the realm. Deep copies is something we are working towards in the longer term though.

@rturk

This comment has been minimized.

Show comment
Hide comment
@rturk

rturk Aug 3, 2016

Has anyone tested using a component Wrapper that contains the actual Realm Query and passes down the results to the component that will actually perform the Render? Provided that the current props vs new props are pure on every event change React should be able to only re-render only what was changed/updated/created.

rturk commented Aug 3, 2016

Has anyone tested using a component Wrapper that contains the actual Realm Query and passes down the results to the component that will actually perform the Render? Provided that the current props vs new props are pure on every event change React should be able to only re-render only what was changed/updated/created.

@rturk

This comment has been minimized.

Show comment
Hide comment
@rturk

rturk Aug 3, 2016

@jwhitley looks like this PR will address some (if not all) your interesting comments regarding force update and events subscribers
#549

rturk commented Aug 3, 2016

@jwhitley looks like this PR will address some (if not all) your interesting comments regarding force update and events subscribers
#549

@andrewvmail

This comment has been minimized.

Show comment
Hide comment
@andrewvmail

andrewvmail Sep 11, 2016

@jwhitley I'm also interested in the details on how you use realm in the context of sagas and how you designed the mechanism of synchronization between the redux store and realm. Ie. when the app starts, offline detection etc. Thanks in advance!

andrewvmail commented Sep 11, 2016

@jwhitley I'm also interested in the details on how you use realm in the context of sagas and how you designed the mechanism of synchronization between the redux store and realm. Ie. when the app starts, offline detection etc. Thanks in advance!

@g6ling

This comment has been minimized.

Show comment
Hide comment
@g6ling

g6ling Jan 14, 2017

In my case, I think realm as API Server.
If I send data, I receive the response, and I update my store with the response.

So I made a function called ImmutableRealm.

// realm/index.js
const realm = new Realm({ schema: [...] });

export const ImmutableRealm = (func, option = {}) => {
  const defaultCopy = (item) => JSON.parse(JSON.stringify(item)); 
  const copy = option.copy || defaultCopy; // Use deep copy.
  const success = option.success || true;
  const fail = option.fail || false;

  const defualtErrorHandler = (e) => e;
  const errorHandler = option.errorHandler || defualtErrorHandler;
  return (props) => new Promise((resolve, reject) => {
    try {
      const result = func(props, realm) || 'Return is null';
      const copiedResult = copy(result);
      resolve({ status: success, data: copiedResult });
    } catch (e) {
      const error = errorHandler(e);
      reject({ status: fail, error });
    }
  });
};

export default realm

and I made Realm's API functions.

export const getFunction = ImmutableRealm(({ id }, realm) => {
  let item = realm.object('Name').filtered(`id == ${id}`)
  return item;
});
// example Todo
export const getCheckedTodoItem = ImmutableRealm(({ listId }, realm) => {
  const list = realm.object('List').filtered(`id == ${listId}`);
  const checkedTodos = list.todos.filtered(`check == true`);
  return checkedTodos;
});

Use this like API.

If you use redux-saga, you can write like below.

const resRealm = yield call(getCheckedTodoItem, { listId });

g6ling commented Jan 14, 2017

In my case, I think realm as API Server.
If I send data, I receive the response, and I update my store with the response.

So I made a function called ImmutableRealm.

// realm/index.js
const realm = new Realm({ schema: [...] });

export const ImmutableRealm = (func, option = {}) => {
  const defaultCopy = (item) => JSON.parse(JSON.stringify(item)); 
  const copy = option.copy || defaultCopy; // Use deep copy.
  const success = option.success || true;
  const fail = option.fail || false;

  const defualtErrorHandler = (e) => e;
  const errorHandler = option.errorHandler || defualtErrorHandler;
  return (props) => new Promise((resolve, reject) => {
    try {
      const result = func(props, realm) || 'Return is null';
      const copiedResult = copy(result);
      resolve({ status: success, data: copiedResult });
    } catch (e) {
      const error = errorHandler(e);
      reject({ status: fail, error });
    }
  });
};

export default realm

and I made Realm's API functions.

export const getFunction = ImmutableRealm(({ id }, realm) => {
  let item = realm.object('Name').filtered(`id == ${id}`)
  return item;
});
// example Todo
export const getCheckedTodoItem = ImmutableRealm(({ listId }, realm) => {
  const list = realm.object('List').filtered(`id == ${listId}`);
  const checkedTodos = list.todos.filtered(`check == true`);
  return checkedTodos;
});

Use this like API.

If you use redux-saga, you can write like below.

const resRealm = yield call(getCheckedTodoItem, { listId });
@amanthegreatone

This comment has been minimized.

Show comment
Hide comment
@amanthegreatone

amanthegreatone Jan 30, 2017

trying to wrap my head around this, came across this repo https://github.com/bosung90/RNStorage.

Can someone strip down to fundamentals and explain how the data flows and how data updates are handled (redux store and realm).

Lets say we get data into realm from a server API. What should be sent from realm to the redux store? Should we send Realm Results or strip down the values and populate normal objects (that dont autoupdate like realm result objects) via redux actions? what should the reducers do?

here's what i think and where i'm stuck...

  1. Get the data from API and push to realm ... lets say its an object having a set of properties that can be modified from the UI

  2. fetch the relevant data from realm and get a results object

  3. push that results object into the store with a populateStore action and reducer

  4. use that results object from the store for UI rendering

  5. This is where I'm stuck...in the UI when something happens to update the state, should we just use the results object from the state and update it directly (result.property = new value) and not have any force updates for the UI? or should we do a thunk action that pushes the change to realm and the corresponding reducer does not have to do anything as the changes reflects automatically in the state because of results object and its autoupdate property?

amanthegreatone commented Jan 30, 2017

trying to wrap my head around this, came across this repo https://github.com/bosung90/RNStorage.

Can someone strip down to fundamentals and explain how the data flows and how data updates are handled (redux store and realm).

Lets say we get data into realm from a server API. What should be sent from realm to the redux store? Should we send Realm Results or strip down the values and populate normal objects (that dont autoupdate like realm result objects) via redux actions? what should the reducers do?

here's what i think and where i'm stuck...

  1. Get the data from API and push to realm ... lets say its an object having a set of properties that can be modified from the UI

  2. fetch the relevant data from realm and get a results object

  3. push that results object into the store with a populateStore action and reducer

  4. use that results object from the store for UI rendering

  5. This is where I'm stuck...in the UI when something happens to update the state, should we just use the results object from the state and update it directly (result.property = new value) and not have any force updates for the UI? or should we do a thunk action that pushes the change to realm and the corresponding reducer does not have to do anything as the changes reflects automatically in the state because of results object and its autoupdate property?

@lodev09

This comment has been minimized.

Show comment
Hide comment
@lodev09

lodev09 Feb 8, 2017

@jwhitley I'm doing exactly what you pointed above before I saw your post. I was googling if I'm doing the "right" thing too with realm + redux and found your post. Definitely ease my doubt with my implementation.

You're right about react-native and redux not knowing that something added/removed to realm's Results so I'm passing it as a "new" state to the reducer so UI gets updated.

I hope realm supports redux in the future -- maybe for performance/efficiency purposes(?)

lodev09 commented Feb 8, 2017

@jwhitley I'm doing exactly what you pointed above before I saw your post. I was googling if I'm doing the "right" thing too with realm + redux and found your post. Definitely ease my doubt with my implementation.

You're right about react-native and redux not knowing that something added/removed to realm's Results so I'm passing it as a "new" state to the reducer so UI gets updated.

I hope realm supports redux in the future -- maybe for performance/efficiency purposes(?)

@cwagner22

This comment has been minimized.

Show comment
Hide comment
@cwagner22

cwagner22 May 7, 2017

@g6ling With your ImmutableRealm function all the arrays (realm lists) are converted to object wich is a bit of a problem because I have to manually convert them back to array. It's the same issue if I use another Immutable library. Any simple way to fix that?

cwagner22 commented May 7, 2017

@g6ling With your ImmutableRealm function all the arrays (realm lists) are converted to object wich is a bit of a problem because I have to manually convert them back to array. It's the same issue if I use another Immutable library. Any simple way to fix that?

@lodev09

This comment has been minimized.

Show comment
Hide comment
@lodev09

lodev09 May 7, 2017

@cwagner22 I don't think you need to convert the realm results at all. I do this on my app i.e. I pass the results/object (from filtered, etc) directly to the reducer and I can still access properties and methods I defined on the objects. But as mentioned above, the objects are not "live" so updates with realm doesn't reflect without a dispatch. I guess redux does its thing already in making the objects "immutable" but still they are realm objects :)

EDIT: I may have explained it wrong about the "live" thing... I was referring to the rendering mechanism. If you update an object via realm, it actually gets reflected but you'll have to dispatch with redux to trigger a render

lodev09 commented May 7, 2017

@cwagner22 I don't think you need to convert the realm results at all. I do this on my app i.e. I pass the results/object (from filtered, etc) directly to the reducer and I can still access properties and methods I defined on the objects. But as mentioned above, the objects are not "live" so updates with realm doesn't reflect without a dispatch. I guess redux does its thing already in making the objects "immutable" but still they are realm objects :)

EDIT: I may have explained it wrong about the "live" thing... I was referring to the rendering mechanism. If you update an object via realm, it actually gets reflected but you'll have to dispatch with redux to trigger a render

@cwagner22

This comment has been minimized.

Show comment
Hide comment
@cwagner22

cwagner22 May 7, 2017

@lodev09 Thanks. Well it looks like the root of my issue is that realm.objects('Car') returns an object and not an array. Same for nested lists. So even if I try to save the results directly in redux I have the same issue.

Side question: When you mention

"methods I defined on the objects."

Do you mean you have created some custom methods for your realm objects? I would love to see an example because I didn't find any way to do that.

cwagner22 commented May 7, 2017

@lodev09 Thanks. Well it looks like the root of my issue is that realm.objects('Car') returns an object and not an array. Same for nested lists. So even if I try to save the results directly in redux I have the same issue.

Side question: When you mention

"methods I defined on the objects."

Do you mean you have created some custom methods for your realm objects? I would love to see an example because I didn't find any way to do that.

@lodev09

This comment has been minimized.

Show comment
Hide comment
@lodev09

lodev09 May 7, 2017

returns an object and not an array

In the realm world, their "results" acts like an array actually i.e. you can do forEach, map, etc. I would suggest you look a their docs for realm-js. They have their own version of a ListView for optimization (standard listview works as well).

Do you mean you have created some custom methods for your realm objects?

yes you can.. I've been doing this to make it more of a standard object instead of an object for realm. You are defining a class so you can just put methods in it and you can call them like a normal object.

Here's an example object definition:

// define your objects
class Car {
  static schema = {
    name: 'Car',
    primaryKey: 'id',
    properties: {
      id: 'int',
      model: 'string',
      name: 'string'
      // ...
    }
  }

  // static methods
  static getCar(id) {
    // see definition below
    return Car.getFromId(id);
  }

  // a method
  changeName(name) {
     realm.write(() => {
      this.name = name;
     });
  }
}

class Person {
  // same stuff
}

const schemas = [
  Car,
  Person
];

// create the realm
const realm = new Realm({
  schema: schemas
});

// this is kinda hacky
// this will basically inject static methods for common realm methods
schemas.forEach((ObjectType) => {
  const schemaName = ObjectType.schema.name;

  ObjectType.get = function() {
    return realm.objects(schemaName);
  }

  ObjectType.getFromId = function(id) {
    return realm.objectForPrimaryKey(schemaName, id);
  }

  // your common realm methods here like inserts, updates, etc.
  // ...
});

Now you can do this:

const cars = Car.get();
if (cars) {
  cars.forEach((car, i) => {
    car.changeName('car index ' + i);
  });
}

// get a single car
const car = Car.getFromId(123);
car.changeName('car 123');

I haven't tested that code but I hope that works for you :)

lodev09 commented May 7, 2017

returns an object and not an array

In the realm world, their "results" acts like an array actually i.e. you can do forEach, map, etc. I would suggest you look a their docs for realm-js. They have their own version of a ListView for optimization (standard listview works as well).

Do you mean you have created some custom methods for your realm objects?

yes you can.. I've been doing this to make it more of a standard object instead of an object for realm. You are defining a class so you can just put methods in it and you can call them like a normal object.

Here's an example object definition:

// define your objects
class Car {
  static schema = {
    name: 'Car',
    primaryKey: 'id',
    properties: {
      id: 'int',
      model: 'string',
      name: 'string'
      // ...
    }
  }

  // static methods
  static getCar(id) {
    // see definition below
    return Car.getFromId(id);
  }

  // a method
  changeName(name) {
     realm.write(() => {
      this.name = name;
     });
  }
}

class Person {
  // same stuff
}

const schemas = [
  Car,
  Person
];

// create the realm
const realm = new Realm({
  schema: schemas
});

// this is kinda hacky
// this will basically inject static methods for common realm methods
schemas.forEach((ObjectType) => {
  const schemaName = ObjectType.schema.name;

  ObjectType.get = function() {
    return realm.objects(schemaName);
  }

  ObjectType.getFromId = function(id) {
    return realm.objectForPrimaryKey(schemaName, id);
  }

  // your common realm methods here like inserts, updates, etc.
  // ...
});

Now you can do this:

const cars = Car.get();
if (cars) {
  cars.forEach((car, i) => {
    car.changeName('car index ' + i);
  });
}

// get a single car
const car = Car.getFromId(123);
car.changeName('car 123');

I haven't tested that code but I hope that works for you :)

@Zhuinden

This comment has been minimized.

Show comment
Hide comment
@Zhuinden

Zhuinden Sep 1, 2017

Redux demands that there is only 1 store, and that all previous states can be re-built from operation history.

Realm works as a store (change event emission on changes) but it doesn't retain history, so it cannot really be used as a redux store. It's more like a flux-store in that regard.

I don't think Realm and Redux are conceptually compatible, although you can create a uni-directional architecture with it if you truly want: https://academy.realm.io/posts/eric-maxwell-uni-directional-architecture-android-using-realm/ otherwise you have to detach the objects to have a history of previous states.

Zhuinden commented Sep 1, 2017

Redux demands that there is only 1 store, and that all previous states can be re-built from operation history.

Realm works as a store (change event emission on changes) but it doesn't retain history, so it cannot really be used as a redux store. It's more like a flux-store in that regard.

I don't think Realm and Redux are conceptually compatible, although you can create a uni-directional architecture with it if you truly want: https://academy.realm.io/posts/eric-maxwell-uni-directional-architecture-android-using-realm/ otherwise you have to detach the objects to have a history of previous states.

@lodev09

This comment has been minimized.

Show comment
Hide comment
@lodev09

lodev09 Sep 1, 2017

I'm treating realm as "extra" functionality to my models, mainly for offline support.

Also, I've refactored my code a long time ago as I've found out that it's not performant to directly pass along realm objects through redux. In short, redux should only accept "plain" objects. A helper method for each model would be useful to map "plain" properties.

During actions, IDs are passed along and recreating a realm object from it if needed.

Hope this helps someone :)

lodev09 commented Sep 1, 2017

I'm treating realm as "extra" functionality to my models, mainly for offline support.

Also, I've refactored my code a long time ago as I've found out that it's not performant to directly pass along realm objects through redux. In short, redux should only accept "plain" objects. A helper method for each model would be useful to map "plain" properties.

During actions, IDs are passed along and recreating a realm object from it if needed.

Hope this helps someone :)

@Zhuinden

This comment has been minimized.

Show comment
Hide comment
@Zhuinden

Zhuinden Sep 1, 2017

Ah, that does make sense. 👍

Zhuinden commented Sep 1, 2017

Ah, that does make sense. 👍

@lodev09

This comment has been minimized.

Show comment
Hide comment
@lodev09

lodev09 Oct 31, 2017

In case anyone is wondering how I "map" properties for redux state objects, here is an updated code from above with property mapping that I use currently.

class Car extends Realm.Object {
  static schema = {
    name: 'Car',
    primaryKey: 'id',
    properties: {
      id: 'int',
      model: 'string',
      name: 'string'
      // ...
    }
  }

  // static methods
  static getCar(id) {
    // see definition below
    return Car.getFromId(id);
  }

  // a method
  changeName(name) {
     realm.write(() => {
      this.name = name;
     });
  }

  // here is where we call the mapping of "pure" properties for redux
  props() {
    return Car.mapProps(this);
  }
}

class Person extends Realm.Object {
  // same stuff
}

const schemas = [
  Car,
  Person
];

// create the realm
const realm = new Realm({
  schema: schemas
});

// this is kinda hacky
// this will basically inject static methods for common realm methods
schemas.forEach((ObjectType) => {
  const schemaName = ObjectType.schema.name;

  ObjectType.get = function() {
    return realm.objects(schemaName);
  }

  ObjectType.getFromId = function(id) {
    return realm.objectForPrimaryKey(schemaName, id);
  }

  // the static method that can be used for every realm objects
  ObjectType.mapProps = function(object, exclude = []) {
    let props = {};
    const propNames = Object.keys(ObjectType.schema.properties).filter(p => exclude.indexOf(p) < 0);

    propNames.forEach((p) => {
      if (typeof object[p] !== 'function') {
        const propSchema = ObjectType.schema.properties[p];
        let type = null;
        if (typeof propSchema === 'string') {
          type = propSchema;
        } else {
          type = propSchema.type;
        }

        switch (type) {
          case 'date':
            props[p] = object[p] && object[p].getTime();
            break;
          default:
            props[p] = object[p];
            break;
        }
      }
    });

    return props;
  }

  // your common realm methods here like inserts, updates, etc.
  // ...
});

Reducer Action side:

// actions
dispatch({
  type: GET_CAR,
  car: car.props()
});

Feel free to modify or comment what you think. 😄

EDIT:
As mentioned from redux docs here, it's best to call the props method in the action side instead in reducer.

lodev09 commented Oct 31, 2017

In case anyone is wondering how I "map" properties for redux state objects, here is an updated code from above with property mapping that I use currently.

class Car extends Realm.Object {
  static schema = {
    name: 'Car',
    primaryKey: 'id',
    properties: {
      id: 'int',
      model: 'string',
      name: 'string'
      // ...
    }
  }

  // static methods
  static getCar(id) {
    // see definition below
    return Car.getFromId(id);
  }

  // a method
  changeName(name) {
     realm.write(() => {
      this.name = name;
     });
  }

  // here is where we call the mapping of "pure" properties for redux
  props() {
    return Car.mapProps(this);
  }
}

class Person extends Realm.Object {
  // same stuff
}

const schemas = [
  Car,
  Person
];

// create the realm
const realm = new Realm({
  schema: schemas
});

// this is kinda hacky
// this will basically inject static methods for common realm methods
schemas.forEach((ObjectType) => {
  const schemaName = ObjectType.schema.name;

  ObjectType.get = function() {
    return realm.objects(schemaName);
  }

  ObjectType.getFromId = function(id) {
    return realm.objectForPrimaryKey(schemaName, id);
  }

  // the static method that can be used for every realm objects
  ObjectType.mapProps = function(object, exclude = []) {
    let props = {};
    const propNames = Object.keys(ObjectType.schema.properties).filter(p => exclude.indexOf(p) < 0);

    propNames.forEach((p) => {
      if (typeof object[p] !== 'function') {
        const propSchema = ObjectType.schema.properties[p];
        let type = null;
        if (typeof propSchema === 'string') {
          type = propSchema;
        } else {
          type = propSchema.type;
        }

        switch (type) {
          case 'date':
            props[p] = object[p] && object[p].getTime();
            break;
          default:
            props[p] = object[p];
            break;
        }
      }
    });

    return props;
  }

  // your common realm methods here like inserts, updates, etc.
  // ...
});

Reducer Action side:

// actions
dispatch({
  type: GET_CAR,
  car: car.props()
});

Feel free to modify or comment what you think. 😄

EDIT:
As mentioned from redux docs here, it's best to call the props method in the action side instead in reducer.

@angelstoone

This comment has been minimized.

Show comment
Hide comment
@angelstoone

angelstoone Nov 10, 2017

What do you think about redux-persist-realm?

angelstoone commented Nov 10, 2017

What do you think about redux-persist-realm?

@fungilation

This comment has been minimized.

Show comment
Hide comment
@fungilation

fungilation Nov 10, 2017

Intriguing! A new project I see. I've been looking for tying redux and realm, using Realm to persist Redux is a natural way to integrate.

fungilation commented Nov 10, 2017

Intriguing! A new project I see. I've been looking for tying redux and realm, using Realm to persist Redux is a natural way to integrate.

@brancooo1

This comment has been minimized.

Show comment
Hide comment
@brancooo1

brancooo1 commented Feb 25, 2018

+1

@brancooo1

This comment has been minimized.

Show comment
Hide comment
@brancooo1

brancooo1 Feb 25, 2018

After bit of investigating I went with redux-thunk - realm solution. I personally like more the idea.

Here's blog post where I got inspiration:
https://medium.com/@manggit/react-native-redux-realm-js-r3-js-a-new-mobile-development-standard-5290ec02a590

brancooo1 commented Feb 25, 2018

After bit of investigating I went with redux-thunk - realm solution. I personally like more the idea.

Here's blog post where I got inspiration:
https://medium.com/@manggit/react-native-redux-realm-js-r3-js-a-new-mobile-development-standard-5290ec02a590

@fungilation

This comment has been minimized.

Show comment
Hide comment
@fungilation

fungilation Feb 26, 2018

I'm now undecided between Firebase (firestore) and realm. I'm already using Firebase without integration with redux. And redux-thunk does look like the best/simplest way to integrate, either one.

fungilation commented Feb 26, 2018

I'm now undecided between Firebase (firestore) and realm. I'm already using Firebase without integration with redux. And redux-thunk does look like the best/simplest way to integrate, either one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment