React-native get all photos from Camera Roll and Camera Roll API - javascript

I am trying to get camera roll photos using react native CameraRoll.getPhotos API. The issue I found that the documentation is not great. In react-native official documentation there are two terms that were mentioned getPhotosReturnChecker and getPhotosParamChecker where I can get the detail about this parameters.
I found the following object that can be passed to CameraRoll.getPhotos from bhwgroup blog
{
first: ..., // (required) The number of photos wanted in reverse order of the photo application
after: ..., // A cursor returned from a previous call to 'getPhotos'
groupTypes: ..., // Specifies which group types to filter the results to
// One of ['Album', 'All', 'Event', 'Faces', 'Library', 'PhotoStream', 'SavedPhotos'(default)]
groupName: ..., // Specifies filter on group names, like 'Recent Photos' or custom album titles
assetType: ... // Specifies filter on assetType
// One of ['All', 'Videos', 'Photos'(default)]
}
According to these it always require a parameter first which dictates how many pictures we can get from CameraRoll. Instead if I want all the photos from camera roll how can I get it?

You'll want to do some paging to access all photos. Basically, you are loading them in chunks, and keeping track of the place where you left off after each fetch. You'll want a state similar to this:
this.state = {
dataSource: ds.cloneWithRows([]),
assets: [],
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
};
Then fetching functions similar to these. This example assumes you are using a ListView to display your photos using a ListView.DataSource
tryPhotoLoad() {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.loadPhotos(); });
}
}
loadPhotos() {
const fetchParams = {
first: 35,
groupTypes: 'SavedPhotos',
assetType: 'Photos',
};
if (Platform.OS === 'android') {
// not supported in android
delete fetchParams.groupTypes;
}
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then((data) => {
this.appendAssets(data);
}).catch((e) => {
console.log(e);
});
}
appendAssets(data) {
const assets = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (assets.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.assets = this.state.assets.concat(assets);
nextState.dataSource = this.state.dataSource.cloneWithRows(
_.chunk(nextState.assets, 3)
);
}
this.setState(nextState);
}
endReached() {
if (!this.state.noMorePhotos) {
this.tryPhotoLoad();
}
}

Related

Custom merge function is not being called after updating field with cache.modify

I have written a custom merge function for the field products on type Session. It seems the merge function is only being called when I initialise the object Session:1 with its products, and not when I update products later using cache.modify.
My merge function:
const client = new ApolloClient({
uri: 'http://localhost:8081/graphql',
cache: new InMemoryCache({
typePolicies: {
Session: {
fields: {
products: {
merge (existing, incoming) {
// this is only being called on useQuery(HydrateSession), not useMutation(UpsertProduct)
console.log('existing', JSON.stringify(existing, null, 2))
console.log('incoming', JSON.stringify(incoming, null, 2))
// remove duplicates when latestProduct has the same id as an existing product — [..., latestProduct]
if (incoming.filter(p => p.id === incoming[incoming.length - 1].id).length > 1) return existing
return incoming
}
}
}
}
}
})
})
Initialisation of Session:
const HydrateSession = gql`
query {
session {
id
products {
id
}
}
}
`
...
useQuery(HydrateSession)
Updating products later using cache.modify:
const UpsertProduct = gql`
mutation UpsertProduct($product: ProductInput!) {
upsertProduct(product: $product) {
id
}
}
`
...
const [upsertProductMutation] = useMutation(UpsertProduct)
const onClick = async () => {
await upsertProductMutation({
variables: {
product: {
id: 2
}
},
update: (cache, mutationResult) => {
cache.modify({
id: 'Session:1',
fields: {
products: previous => [...previous, mutationResult.data.upsertProduct]
}
})
}
})
}
I have a full working example here https://github.com/jsindos/apollo-play, run npm i and then start two separate processes with npm start and npm serve. After clicking the button triggering the mutation, the merge function is not run (as seen by the absence of console.log statements in the console).
modify circumvents any merge functions you've defined, which means that fields are always overwritten with exactly the values you specify.
https://www.apollographql.com/docs/react/caching/cache-interaction/#using-cachemodify
Reading documentation is a good thing.

Apollo GraphQL merge cached data

I have a page that consists of 2 components and each of them has its own request for data
for example
<MovieInfo movieId={queryParamsId}/>
const GET_MOVIE_INFO = `gql
query($id: String!){
movie(id: $id){
name
description
}
}`
Next component
<MovieActors movieId={queryParamsId}/>
const GET_MOVIE_ACTORS = `gql
query($id: String!){
movie(id: $id){
actors
}
}`
For each of these queries I use apollo hook
const { data, loading, error } = useQuery(GET_DATA, {variable: {id: queryParamsId}}))
Everything is fine, but I got a warning message:
Cache data may be lost when replacing the movie field of a Query object.
To address this problem (which is not a bug in Apollo Client), either ensure all objects of type Movie have IDs, or define a custom merge function for the Query.movie field, so InMemoryCache can safely merge these objects: { ... }
It's works ok with google chrome, but this error affects Safari browser. Everything is crushing. I'm 100% sure it's because of this warning message. On the first request, I set Movie data in the cache, on the second request to the same query I just replace old data with new, so previous cached data is undefined. How can I resolve this problem?
Here is the same solution mentioned by Thomas but a bit shorter
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
// shorthand
merge: true,
},
},
},
},
});
This is same as the following
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
merge(existing, incoming, { mergeObjects }) {
return mergeObjects(existing, incoming);
},
},
},
},
},
});
Solved!
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
YOUR_FIELD: {
merge(existing = [], incoming: any) {
return { ...existing, ...incoming };
// this part of code is depends what you actually need to do, in my
case i had to save my incoming data as single object in cache
}
}
}
}
}
})
});
The other answers still work, but as of Apollo Client >= 3.3 there's an easier option that doesn't require specifying specific fields or a custom merge function. Instead, you only have to specify the type and it will merge all fields for that type:
const cache = new InMemoryCache({
typePolicies: {
YOUR_TYPE_NAME: {
merge: true,
}
}
});
From your example query, I'd guess that an id field should be available though? Try requesting the ID in your query, that should solve the problem in a much more ideal way.
Had same issue with inconsistency of data values vs. our schema. A value type within an entity was missing the id value. Caused by an incomplete data migration.
Temporary solution:
const typePolicies = {
PROBLEM_TYPE: {
keyFields: false as false,
},
PARENT_TYPE: {
fields: {
PROBLEM_FIELD: {
merge: true
}
}
}
}

How to fetch multiple conditional queries in Apollo Client React?

I'm using Apollo Client, and for fetching queries I'm using useQuery from the package #apollo/react-hooks.
I would like to accomplish the following:
List of Steps:
Step 1: Fetch a query stage
const GetStage = useQuery(confirmStageQuery, {
variables: {
input: {
id: getId.id
}
}
});
Step 2: Based on the response that we get from GetStage, we would like to switch between 2 separate queries
if (!GetStage.loading && GetStage.data.getGame.stage === "Created") {
Details = useQuery(Query1, {
variables: {
input: {
id: getId.id
}
}
});
} else if (!GetStage.loading && GetStage.data.getGame.stage === "Confirmed") {
Details = useQuery(Query2, {
variables: {
input: {
id: getId.id
}
}
});
}
Step 3: Also when the page loads every time, I'm re-fetching the data.
useEffect(() => {
//Fetch for Change in the Stage
GetStage.refetch();
//Fetch for Change in the Object
if (Details) {
Details.refetch();
if (Details.data) {
setDetails(Details.data.getGame);
}
}
});
Problem?
Rendered more hooks than during the previous render.
Details.data is undefined
So how can we call multiple async queries in Apollo Client?
As Philip said, you can't conditionally call hooks. However conditionally calling a query is very common, so Apollo allows you to skip it using the skip option:
const { loading, error, data: { forum } = {}, subscribeToMore } = useQuery(GET_FORUM, {
skip: !forumId,
fetchPolicy: 'cache-and-network',
variables: { id: forumId },
});
The hook is called, but the query isn't. That's a lot simpler and clearer than using a lazy query for your use case in my opinion.
The rules of hooks say you can't conditionally call hooks, whenever you find yourself in a situation where you want to use an if/else around a hook you're probably on the wrong track.
What you want to do here is to use a lazyQuery for everything that's "optional" or will be fetched later - or for queries that depend on the result of another query.
Here's a quick example (probably not complete enough to make your entire code work):
// This query is always called, use useQuery
const GetStage = useQuery(confirmStageQuery, {
variables: {
input: {
id: getId.id
}
}
});
const [fetchQuery1, { loading1, data1 }] = useLazyQuery(Query1);
const [fetchQuery2, { loading2, data2 }] = useLazyQuery(Query2);
// Use an effect to execute the second query when the data of the first one comes in
useEffect(() => {
if (!GetStage.loading && GetStage.data.getGame.stage === "Created") {
fetchQuery1({variables: {
input: {
id: getId.id
}
}})
} else if (!GetStage.loading && GetStage.data.getGame.stage === "Confirmed") {
fetchQuery2({variables: {
input: {
id: getId.id
}
}})
}
}, [GetState.data, GetStage.loading])

Using sequelize belongsTo and hasOne results in a Maximum call stack size exceeded

In my Conversation model, I have:
Conversation.hasOne(sequelize.import('./audio'), { constraints: false })
And in Audio, I have:
Audio.belongsTo(sequelize.import('./conversation'))
To load these, from another file, I'm doing:
fs.readdirSync(`${__dirname}`).forEach((modelFile) => {
if (path.extname(modelFile) === '.js' && modelFile !== 'index.js') {
sequelize.import(`./${modelFile}`)
}
})
so I can't guarantee order. But shouldn't the {constraints: false} any cyclical dependencies?
If I remove either the belongsTo or the hasOne, then everything works fine (sort of). If I have just the belongsTo, then I can't do a query like:
return db.models.Conversation.findAll({
where: {
status: {
$notIn: ['ready', 'error']
}
},
include: [{
model: db.models.Audio
}]
})
It complains that Audio is not associated to Conversation. Ideally, I want them both to be related to each other.
I am using v4, btw
See example:
const Player = this.sequelize.define('player', {/* attributes */});
const Team = this.sequelize.define('team', {/* attributes */});
Player.belongsTo(Team); // Will add a teamId attribute to Player to hold the primary key value for Team
In your case:
const Audio = require("./audio");
Conversation.hasOne(Audio, { constraints: false });
const Conversation = require("./conversation");
Audio.belongsTo(Conversation);

VueX: How to architecture my store with nested objects

I am currently coding an application in VueJS (and with Vuex in particular). However, my question is not strongly linked to this library, but rather to the architecture to have with a store like flux/redux/Vuex.
To put it simply, I have several APIs (one API/database per team), and for each team/API, I have several users.These teams and users are represented by simple objects, and each has its own slug. Important note: the slugs of the teams are of course unique, but the slugs users are unique for their own team. The uniqueness constraint for a user would then be "teamSlug/userSlug". And given the large number of users, I can not simply load all the users of all the teams.
My question is how to properly architect my application/store in order to recover the data of a given user slug (with his team): if I have not already loaded this user, make an API request to retrieve it. Currently I have created a getter that returns the user object, which takes the slug from the user and the team. If it returns "null" or with a ".loading" to "false", I have to run the "loadOne" action that will take care of retrieving it:
import * as types from '../../mutation-types'
import users from '../../../api/users'
// initial state
const state = {
users: {}
}
// getters
const getters = {
getOne: state => (team, slug) => (state.users[team] || {})[slug] || null
}
// actions
const actions = {
loadOne ({ commit, state }, { team, slug }) {
commit(types.TEAM_USER_REQUEST, { team, slug })
users.getOne(team, slug)
.then(data => commit(types.TEAM_USER_SUCCESS, { team, slug, data }))
.catch(error => commit(types.TEAM_USER_FAILURE, { team, slug, error }))
}
}
// mutations
const mutations = {
[types.TEAM_USER_REQUEST] (state, { team, slug }) {
state.users = {
...state.users,
[team]: {
...(state.users[team] || {}),
[slug]: {
loading: true,
error: null,
slug
}
}
}
},
[types.TEAM_USER_SUCCESS] (state, { team, slug, data }) {
state.users = {
...state.users,
[team]: {
...(state.users[team] || {}),
[slug]: {
...data,
slug,
loading: false
}
}
}
},
[types.TEAM_USER_FAILURE] (state, { team, slug, error }) {
state.users = {
...state.users,
[team]: {
...(state.users[team] || {}),
[slug]: {
slug,
loading: false,
error
}
}
}
}
}
export default {
namespaced: true,
state,
getters,
actions,
mutations
}
You imagine that a team does not only have users, I have many other models of that type, and I should link them together. This method works, but I find it rather cumbersome to put in place (especially that it is a simple get, I will have plenty of other actions of this kind). Would you have any advice on my architecture?
Thank you!
I've found that the best way to keep the Vuex store flexible is to normalize it and keep your data items as flat as possible. That means storing all of your users in one structure and finding a way to uniquely identify them.
What if we combine the team and user slug to create a unique identifier? Here's how I imagine your users with a red team and a blue team:
const state = {
users: {
allTeamSlugs: [
'blue1',
'blue2',
'blue3',
'red1',
'red2',
// etc...
],
byTeamSlug: {
blue1: {
slug: 1,
team: 'blue',
teamSlug: 'blue1'
},
// blue2, blue3, etc...
red1: {
slug: 1,
team: 'red',
teamSlug: 'red1'
},
// red2, etc...
}
}
}
And the teamSlug property doesn't need to exist for each user in your API. You can create it in your mutation as you load data into the store.
const mutations = {
[types.TEAM_USER_SUCCESS] (state, { team, slug, data }) {
const teamSlug = [team, slug].join('')
state.users.byTeamSlug = {
...state.users.byTeamSlug,
[teamSlug]: {
...data,
slug: slug,
team: team,
teamSlug: teamSlug
}
}
state.users.allTeamSlugs = [
...new Set([ // The Set ensures uniqueness
...state.users.allTeamSlugs,
teamSlug
])
]
},
// ...
}
Then your getters might work like this:
const getters = {
allUsers: state => {
return state.users.allTeamSlugs.map((teamSlug) => {
return state.users.byTeamSlug[teamSlug];
});
},
usersByTeam: (state, getters) => (inputTeam) => {
return getters.allUsers.filter((user) => user.team === inputTeam);
},
getOne: state => (team, slug) => { // or maybe "userByTeamSlug"?
const teamSlug = [team, slug].join('');
return state.users.byTeamSlug[teamSlug]; // or undefined if none found
}
}
Redux has a great article about normalization that I always find myself coming back to: https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape#designing-a-normalized-state

Categories

Resources