Skip to content
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

Update example apps to show Realm integrated with the latest React Native APIs (React Hooks, FlatList, ...) #2345

Open
ferrannp opened this issue Apr 20, 2019 · 11 comments

Comments

@ferrannp
Copy link

ferrannp commented Apr 20, 2019

I created this in stackoverflow: https://stackoverflow.com/questions/55741282/flatlist-not-updating-with-react-hooks-and-realm

Basically, I created a React Hook to access Realm:

/* @flow */

import { useState, useEffect } from 'react';

export default function useRealmResultsHook(query, args): Array {
  const [data, setData] = useState(args ? query(...args) : query());

  useEffect(
    () => {
      function handleChange(newData) {
        setData(newData);
      }

      const dataQuery = args ? query(...args) : query();
      dataQuery.addListener(handleChange);
      return () => {
        dataQuery.removeAllListeners();
      };
    },
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [query, ...args]
  );

  return data;
}

And then I use it:

const MyComponent = (props: Props) => {
  const data = useRealmResultsHook(getDataByType, [props.type]);

  return (
    <View>
      <FlatList
        data={data}
        keyExtractor={keyExtractor}
        renderItem={renderItem}
      />
    </View>
  );
};

Long story short, FlatList does not update because it looks like {{data === newData }} (reference) as Realm mutates the result (not creating a new copy).

Like this works:

setData([...newData]);

But doing so I get a plain JS array, not Realm Results anymore. What is the best way to do this? How to better clone Realm Results? I am afraid that [...newData] might be quite inefficient for big amounts of data.

TBH, it looks to be nothing about the hook but just Realm

  • FlatList (FlatList expects always a new array reference in order to update, not a mutated on).
@zek
Copy link

zek commented Jul 3, 2019

I believe we also need a sample without hooks. It is not clear how to use realmjs with react-native clearly. Examples are deprecated they must be updated

@sunhak-hout
Copy link

sunhak-hout commented Jul 13, 2019

I just recently met this problem and I figured out this is the problem with React Hook but I don't know yet why it is. If you use class component and use this.setState(), this will work fine.

Anyway, I've also found the solution with React Hook.

You can try converting Realm Results into an array of Realm Object by doing so:

setData(newData.slice());

@zek
Copy link

zek commented Jul 13, 2019

@sunhak-hout but what if we have a 100k rows or something like that. I believe we need a mobx integration to realm

@ferrannp
Copy link
Author

This is the workaround I ended up using:

function handleChange() {
  setData({
    data: newData,
    // Realm mutates the array instead of returning a new copy,
    // thus for a FlatList to update, we can use a timestamp as
    // extraData prop
    timestamp: Date.now(),
  });
}

And then in FlatList:

<FlatList
  ...
  extraData={timestamp}
/>

In this way, every time Realm updates, I force FlatList to update.

@cybercoder
Copy link

It seems pagination is a problem now with using array slice. hmmm

@Divyanshmt
Copy link

@ferrannp Thank you so much 🥇 💯

@realm realm deleted a comment from realm-probot bot Feb 27, 2020
@kraenhansen
Copy link
Member

@ferrannp @zek I've been working on an update for our "My First Ream App" example over at https://github.com/realm/my-first-realm-app/tree/kh/updated-react-native ... I've made a PR (realm/my-first-realm-app#64) with the changes - any comments on that would be greatly appreciated!

@kraenhansen kraenhansen reopened this Feb 27, 2020
@kraenhansen kraenhansen changed the title What is the best way to use with React Hooks and FlatList? Update example apps to show Realm integrated with the latest React Native APIs (React Hooks, FlatList, ...) Feb 27, 2020
@kraenhansen kraenhansen added T-Doc and removed T-Help labels Feb 27, 2020
@EzeMortal
Copy link

If someone want to apply the workaround from ferrannp into a component:

realm init

import RealmDatabase from 'realm'

const realm = new RealmDatabase({
   schema: [
      {name: 'Dog', properties: {name: 'string', age: 'int'}}
   ],
   schemaVersion: 2
})

query

const query = () => realm.objects('Dog')

useRealmResultsHook

function useRealmResultsHook(query, args = []) {
   const [data, setData] = useState({
      data: args ? query(...args) : query(),
      a: Date.now()
   })

   useEffect(() => {
      function handleChange(newData) {
         setData({
            data: newData,
            a: Date.now()     // the "hacky" fix, this will create a
         })                   // different object and execute a re-render
      }

      const dataQuery = args ? query(...args) : query()

      dataQuery.addListener(handleChange)

      return () => {
         dataQuery.removeAllListeners()
      }
   }, [query, ...args])

   return data.data           // this hook will return only the data from realm
}

component

const MyComponent = () => {
   const data = useRealmResultsHook(query)

   // function to show the average age of all dogs
   // this is to illustrate that the 'data' is still being a 'Results' class
   const showAge = () => {
      if (data.length > 0) {
         // we use the avg() inherited method from 'Results' class
         return data.avg('age').toFixed(2)
      }

      return 0
   }

   const handlePress = () => {
      realm.write(() => {
         // a dog live between 1 to 12 years :(
         const age = randomInt(1, 12)

         realm.create('Dog', {
            name: 'Rex',
            age: age
         })

         console.log(`inserted dog with age ${age}`)
      })
   }

   return (
      <View style={styles.container}>
         <Text style={styles.text}>{`Dogs lenght: ${data?.length}`}</Text>
         <Text style={styles.text}>{`Dogs avg age: ${showAge()}`}</Text>

         <Button title="Increment" onPress={handlePress}/>
      </View>
   )
}

@shahzaibmuneeb
Copy link

shahzaibmuneeb commented Apr 26, 2020

If someone want to apply the workaround from ferrannp into a component:

realm init

import RealmDatabase from 'realm'

const realm = new RealmDatabase({
   schema: [
      {name: 'Dog', properties: {name: 'string', age: 'int'}}
   ],
   schemaVersion: 2
})

query

const query = () => realm.objects('Dog')

useRealmResultsHook

function useRealmResultsHook(query, args = []) {
   const [data, setData] = useState({
      data: args ? query(...args) : query(),
      a: Date.now()
   })

   useEffect(() => {
      function handleChange(newData) {
         setData({
            data: newData,
            a: Date.now()     // the "hacky" fix, this will create a
         })                   // different object and execute a re-render
      }

      const dataQuery = args ? query(...args) : query()

      dataQuery.addListener(handleChange)

      return () => {
         dataQuery.removeAllListeners()
      }
   }, [query, ...args])

   return data.data           // this hook will return only the data from realm
}

component

const MyComponent = () => {
   const data = useRealmResultsHook(query)

   // function to show the average age of all dogs
   // this is to illustrate that the 'data' is still being a 'Results' class
   const showAge = () => {
      if (data.length > 0) {
         // we use the avg() inherited method from 'Results' class
         return data.avg('age').toFixed(2)
      }

      return 0
   }

   const handlePress = () => {
      realm.write(() => {
         // a dog live between 1 to 12 years :(
         const age = randomInt(1, 12)

         realm.create('Dog', {
            name: 'Rex',
            age: age
         })

         console.log(`inserted dog with age ${age}`)
      })
   }

   return (
      <View style={styles.container}>
         <Text style={styles.text}>{`Dogs lenght: ${data?.length}`}</Text>
         <Text style={styles.text}>{`Dogs avg age: ${showAge()}`}</Text>

         <Button title="Increment" onPress={handlePress}/>
      </View>
   )
}

Instead of using the hacky fix with Date.now() why not use a real updating value such as the changes?

 const handleChange = (newData, newChanges) => {
     setState({
         data: newData,
         changes: newChanges,
    });
};

@EzeMortal
Copy link

If someone want to apply the workaround from ferrannp into a component:
realm init

import RealmDatabase from 'realm'

const realm = new RealmDatabase({
   schema: [
      {name: 'Dog', properties: {name: 'string', age: 'int'}}
   ],
   schemaVersion: 2
})

query

const query = () => realm.objects('Dog')

useRealmResultsHook

function useRealmResultsHook(query, args = []) {
   const [data, setData] = useState({
      data: args ? query(...args) : query(),
      a: Date.now()
   })

   useEffect(() => {
      function handleChange(newData) {
         setData({
            data: newData,
            a: Date.now()     // the "hacky" fix, this will create a
         })                   // different object and execute a re-render
      }

      const dataQuery = args ? query(...args) : query()

      dataQuery.addListener(handleChange)

      return () => {
         dataQuery.removeAllListeners()
      }
   }, [query, ...args])

   return data.data           // this hook will return only the data from realm
}

component

const MyComponent = () => {
   const data = useRealmResultsHook(query)

   // function to show the average age of all dogs
   // this is to illustrate that the 'data' is still being a 'Results' class
   const showAge = () => {
      if (data.length > 0) {
         // we use the avg() inherited method from 'Results' class
         return data.avg('age').toFixed(2)
      }

      return 0
   }

   const handlePress = () => {
      realm.write(() => {
         // a dog live between 1 to 12 years :(
         const age = randomInt(1, 12)

         realm.create('Dog', {
            name: 'Rex',
            age: age
         })

         console.log(`inserted dog with age ${age}`)
      })
   }

   return (
      <View style={styles.container}>
         <Text style={styles.text}>{`Dogs lenght: ${data?.length}`}</Text>
         <Text style={styles.text}>{`Dogs avg age: ${showAge()}`}</Text>

         <Button title="Increment" onPress={handlePress}/>
      </View>
   )
}

Instead of using the hacky fix with Date.now() why not use a real updating value such as the changes?

 const handleChange = (newData, newChanges) => {
     setState({
         data: newData,
         changes: newChanges,
    });
};

Cool! That's working? You tried?

@shahzaibmuneeb
Copy link

If someone want to apply the workaround from ferrannp into a component:
realm init

import RealmDatabase from 'realm'

const realm = new RealmDatabase({
   schema: [
      {name: 'Dog', properties: {name: 'string', age: 'int'}}
   ],
   schemaVersion: 2
})

query

const query = () => realm.objects('Dog')

useRealmResultsHook

function useRealmResultsHook(query, args = []) {
   const [data, setData] = useState({
      data: args ? query(...args) : query(),
      a: Date.now()
   })

   useEffect(() => {
      function handleChange(newData) {
         setData({
            data: newData,
            a: Date.now()     // the "hacky" fix, this will create a
         })                   // different object and execute a re-render
      }

      const dataQuery = args ? query(...args) : query()

      dataQuery.addListener(handleChange)

      return () => {
         dataQuery.removeAllListeners()
      }
   }, [query, ...args])

   return data.data           // this hook will return only the data from realm
}

component

const MyComponent = () => {
   const data = useRealmResultsHook(query)

   // function to show the average age of all dogs
   // this is to illustrate that the 'data' is still being a 'Results' class
   const showAge = () => {
      if (data.length > 0) {
         // we use the avg() inherited method from 'Results' class
         return data.avg('age').toFixed(2)
      }

      return 0
   }

   const handlePress = () => {
      realm.write(() => {
         // a dog live between 1 to 12 years :(
         const age = randomInt(1, 12)

         realm.create('Dog', {
            name: 'Rex',
            age: age
         })

         console.log(`inserted dog with age ${age}`)
      })
   }

   return (
      <View style={styles.container}>
         <Text style={styles.text}>{`Dogs lenght: ${data?.length}`}</Text>
         <Text style={styles.text}>{`Dogs avg age: ${showAge()}`}</Text>

         <Button title="Increment" onPress={handlePress}/>
      </View>
   )
}

Instead of using the hacky fix with Date.now() why not use a real updating value such as the changes?

 const handleChange = (newData, newChanges) => {
     setState({
         data: newData,
         changes: newChanges,
    });
};

Cool! That's working? You tried?

Yeah! As the changes is updated every change so it re-renders the UI for me.

@realm realm locked and limited conversation to collaborators Apr 27, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

9 participants