How to update Apollo cache after query? - javascript

How can I override a value in the Apollo cache?
I have a graphql query to fetch the user. This returns a user with a default currency. This currency can then be override from a select dropdown.
The query fetches paymentCurrencies from an API, then uses a client side resolver to set the first item in the array of paymentCurrencies to be the users currency
query me {
me {
username
currency #client
paymentCurrencies
}
}
When somebody selects a currency from the dropdown menu, I want to over the users currency with whatever they have selected.
I have something like this so far:
const onChange = e => {
const { value } = e.target
client.writeData({ user: { currency: value, username, __typename: "User" } })
}
I get the following error: Error writing result to store for query:
{"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"GeneratedClientQuery"},"selectionSet":null}]}
Cannot read property 'selections' of null
Is using writeData is the correct method or should I be using writeQuery or something?

As described in the other answer you probably want a simple query and mutation setup. The client directive is used to extend your schema to hold client-only additional data. From your explanation, it sounds like you explicitly want this data to be syncronised with the server.
const ME_QUERY = gql`
query me {
me {
username
currency
paymentCurrencies
}
}
`;
const CURRENCY_MUTATION = gql`
mutation setCurrency($currency: String) {
setCurrency(currency: $currency) {
me {
username
currency
}
}
}
`;
function MyComponent() {
const { data } = useQuery(ME_QUERY);
const [setCurrency] = useMutation(CURRENCY_MUTATION);
const onChange = e => setCurrency({
variables: { currency: e.currentTarget.value },
});
return (
<>
<h2>{data && data.me.currency}</h2>
<select onChange={onChange}>
{/* your dropdown logic */}
</select>
</>
);
}
You get the idea. Apollo will now automatically update your cache. Make sure your mutation allows to query the updated user object.
For the automatic update to work your user needs to be recognised by the cache. You can do that by either adding an id field and selecting it in both the query and the mutation or implementing a dataIdFromObject function in Apollo Client 2.x that includes the username for __typename === 'User' or by using a type policy in Apollo Client 3.x. Find the docs here.

writeData should be used for changing fields at the root, for example:
{
yourState #client
}
In this case, you should use writeQuery. Additionally, this logic should really be extracted into a (local) mutation you can then call inside your component. When using writeQuery, the basic idea is to grab the existing data, make a copy and then transform it as needed:
const { me } = client.readQuery({ query: ME_QUERY })
const data = {
me: {
...me,
currency: value,
}
}
client.writeQuery({ query: ME_QUERY, data })
You can also use writeFragment to directly modify a single instance of an object in the cache. However, you need the object's cache key for this. Because the cache key is derived from the __typename and an id field, you should make sure the query includes an id field first. This is good practice regardless to ensure your cache can be updated easily (see here for more details). Then you can do something like this:
client.writeFragment({
id: 'User:42',
fragment: gql`
fragment UserCurrency on User {
currency #client
}
`,
data: {
currency: value,
},
})

It depends.
For persistent change (sync to server) you should just change user setting using mutation.
For session aware change - don't use user settings - copy this (from logged user properties) to separate value in global app state (redux/mobx or as in this case apollo local state).
In both cases rare problem can be with updating many components using changed data.
Redux/mobx solves this automatically.
Apollo HOCs won't rerender.
Hooks - updates partially (only the one with useQuery and useMutation pair), others will be updated only when rerendered.
Components build with <Query/> creates internally an observable then they will be updated, too.
Mutation have update and refetchQueries parameters for.
There is also a package for more complex use cases.

Related

Using a value from a previous query in react query

I have a react query to get user data like this
const { data: queryInfo, status: queryInfoLoading } = useQuery('users', () =>
getUsers()),
);
I then have a sibling component that needs the same data from the get users query. Is there a way to get the results of the get users query without re-running the query?
Essentially, I would like to do something like this
const userResults = dataFromUserQuery
const { data: newInfo, status: newInfoLoading } = useQuery('newUserData', () =>
getNewUsers(userResults.name)),
)
As suggested in this related question (how can i access my queries from react-query?), writing a custom hook and reusing it wherever you need the data is the recommended approach.
Per default, react-query will trigger a background refetch when a new subscriber mounts to keep the data in the cache up-to-date. You can set a staleTime on the query to tell the library how long some data is considered fresh. In that time, the data will always come from the cache if it exists and no refreshes will be triggered.

Is it possible to use references as react component's prop or state?

I want to create react table component which values are derived from single array object. Is it possible to control the component from view side? My goal is that every user using this component in their web browsers share the same data via singleton view object.
Program modeling is like below.
Database - there are single database in server which contain extinct and independent values.
DataView - there are singleton View class which reflects Database's table and additional dependent data like (sum, average)
Table - I'll build react component which looks like table. And it will show View's data with supporting sorting, filtering, editing and deleting row(s) feature (and more). Also it dose not have actual data, only have reference of data from View(Via shallow copy -- This is my question, is it possible?)
My intentions are,
- When user changes value from table, it is queried to DB by View, and if succeed, View will refer updated data and change it's value to new value and notify to Table to redraw it's contents. -- I mean redraw, not updating value and redraw.
- When values in View are changed with DB interaction by user request, there are no need to update component's value cause the components actually dose not have values, only have references to values (Like C's pointer). So only View should do is just say to Component to redraw it's contents.
I heard that React's component prop should be immutable. (Otherwise, state is mutable) My goal is storing references to component's real value to it's props so that there are no additional operation for reflecting View's data into Table.
It is concept problems, and I wonder if it is possible. Since javascript dose not support pointer officially(Am I right?), I'm not sure if it is possible.
View class is like below,
const db_pool = require('instantiated-singleton-db-pool-interface')
class DataView {
constructor() {
this.sessions = ['user1', 'user2'] // Managing current user who see the table
this.data = [ // This is View's data
{id:1, name:'James', phone:'12345678', bank:2000, cash:300, total:2300,..},
{id:2, name:'Michael', phone:'56785678', bank:2500, cash:250, total:2300,..},
{id:3, name:'Tyson', phone:'23455432', bank:2000, cash:50, total:2300,..}
] // Note that 'total' is not in db, it is calculated --`dependent data`
}
notifySessionToUpdate(ids) {
// ids : list of data id need to be updated
this.sessions.forEach((session) => {
session.onNotifiedUpdateRow(ids) // Call each sessions's
})
}
requestUpdateRow(row, changed_value) {
// I didn't write async, exception related code in this function for simple to see.
update_result = db_pool.update('UPDATE myTable set bank=2500 where id=1')
if (update_result === 'fail') return; // Do Nothing
select_result = db_pool.select('SELECT * from myTable where id=1') // Retrieve updated single data which object scheme is identical with this.data's data
for (k in Object.keys(select_result)) {.ASSIGN_TO_row_IF_VALUE_ARE_DIFFERENT.} // I'm not sure if it is possible in shallow copy way either.
calc.reCalculateRow(row) // Return nothing just recalculate dependant value in this.data which is updated right above.
// Notify to session
this.notifySessionToUpdate([1]) // Each component will update table if user are singing id=1's data if not seeing, it will not. [1] means id:1 data.
return // Success
}
... // other View features
}
Regarding session part, I'm checking how to implement sessionizing(?) the each user and it's component who is communicating with server. So I cannot provide further codes about that. Sorry. I'm considering implementing another shallow copied UserView between React Component Table and DataView(And also I think it helps to do something with user contents infos like sorting preference and etc...)
Regarding DB code, it is class which nest it's pool and query interface.
My problem is that I'm not familiar with javascript. So I'm not sure shallow copy is actually implementable in all cases which I confront with.
I need to think about,
1. Dose javascript fully support shallowcopy in consistent way? I mean like pointer, guarantee check value is reference or not.
2. Dose react's component can be used like this way? Whether using props or state Can this be fullfilled?
Actually, I strongly it is not possible to do that. But I want to check your opinions. Seems it is so C language-like way of thinking.
Redraw mean re-render. You can expose setState() or dispatch() functions from Table component and call them on View level using refs:
function View() {
const ref = useRef();
const onDbResponse = data => ref.current.update(data);
return (
<Table ref={ ref } />
);
}
const Table = React.forwardRef((props, ref) => {
const [ data, setData ] = useState([]);
useImperativeHandler(ref, {
update: setData
});
...
});
Anyway i don't think it's a good practice to update like that. Why can't you just put your data in some global context and use there?
const Context = React.createContext({ value: null, query: () => {} });
const Provider = ({ children }) => {
const [ value, setValue ] = useState();
const query = useCallback(async (request) => {
setValue(await DB.request(request));
}, [ DB ]);
const context = { value, query };
return <Context.Provider value={ context }>{ children }</Context.Provider>;
}
const useDB = () => useContext(Context);
const View = () => {
const { request } = useDB();
request(...);
}
const Table = () => {
const { value } = useDB();
...
}

(GraphQL) - Why does Apollo check the cache first, before doing anything else?

We are developing APIs using Apollo GraphQL. We use the out of the box caching solution that Apollo provides (KeyValueCache using a Redis datastore).
When a request query arrives, why does ApolloServer check the cache first before it does anything else?
Is there any way to insert logic before the cache is touched? For example, we want to do some authentication and permissions checking before Apollo checks the cache.
(Yes, there are directives but we find Public/Private scope and maxAge insufficient for our needs.)
The code and explanation below flags a few different approaches for you to explore -- hopefully one will suit your needs (I am assuming you know you can control whether Apollo looks to cache first by fetchPolicy - although I discuss this briefly below). First, consider using a HOC that checks permissions and authentication prior to returning the passed Component. The permissions/auth data can be passed as props should the passed Component be rendered.
withUserData = Component => {
const { isValidated, userData } = checkAuthAndPermissions(); // Modify for your implementation
if (!isValidated) return null;
return <Component userData={userData} />
}
You can thereafter wrap any Component that needs to make the auth/permission check with the HOC, as shown below. As also shown below, Apollo provides the opportunity to skip the query altogether looking to props or other logic, if that is something you may consider. Finally, through the option prop, you have the ability to set the fetchPolicy, which could be dynamically based on a permission check or props. With this fetchPolicy you could avoid looking to cache if that is an objective.
const ComponentWithApollo = graphql(YOUR_QUERY, {
skip: props => { /* consider permissions/auth here, skip if needed */ },
options: props => {
const fetchPolicy = determineFetchPolicyFromAuthOrPermissions();
return { fetchPolicy };
},
props: ({ data }) => data
})(YourComponent);
withUserData(ComponentWithApollo);

Apollo-client local state cache race condition?

So I have been trying to use apollo-boost in a React app to use the cache to manage my client state using #client directives on queries but I have been having some issues.
Basically I'm using writeQuery() to write a boolean to my local app state in a Component (let's call it component A) and want to get that value in another Component (let's call it component B) using readQuery() inside the componentDidUpdate method. The thing is, readQuery() in Component B is running before writeQuery in Component A sets the value in the cache/local state so the value read by Component B comes out wrong.
I've confirmed this by using setTimeout to delay the readQuery() and indeed after using the timeout, the value is correct, but this solution can't be trusted, I'm probably not aware of something in Apollo Client because this functionality is pretty basic for local state management. Any Tips?
I believe that in Redux this is solved because the state is being injected to props, which makes the component update, so being that Component A is the one that changes the state, component B wouldn't even have to use componentDidUpdate to get the new value, since the state would be injected and Component B would get updated with the correct value.
Any help would be appreciated, sorry if I didn't make myself clear!
EDIT: The writeQuery() is being used inside a Mutation resolver.
Methods like readQuery and writeQuery are meant to be used to read and modify the cache inside of mutations. In general, they should not be used inside of components directly. By calling readQuery, you are only fetching the data from the cache once. Instead, you should utilize a Query component.
const TODO_QUERY = gql`
query GetTodos {
todos #client {
id
text
completed
}
}
`
<Query query={TODO_QUERY}>
{({ data }) => {
if (data.todos) return <ToDoListComponent todos={data.todos}/>
return null
}}
</Query>
The Query component subscribes to relevant changes to the cache, so the value of data will update when your cache does.
Similarly, you should create appropriate mutations for whatever changes to the cache you're going to make, and then utilize a Mutation component to actually mutate the cache.
const client = new ApolloClient({
clientState: {
defaults: {
todos: []
},
resolvers: {
Mutation: {
addTodo: (_, { text }, { cache }) => {
const previous = cache.readQuery({ query: TODO_QUERY })
const newTodo = { id: nextTodoId++, text, completed: false, __typename: 'TodoItem' }
const data = {
todos: previous.todos.concat([newTodo]),
}
cache.writeQuery({ query, data })
return newTodo
},
},
}
}
})
<Mutation mutation={ADD_TODO}>
{(addTodo) => (
// use addTodo to mutate the cache asynchronously
)}
</Mutation>
Please review the docs for more details.

GraphQL - Syntax Error GraphQL request (5:15) Expected Name

I am trying to implement a graphQL API, it went well with queries but it's going not that well with mutations:
Here is my basic mutation using apollo-client and graphql-tag:
import gql from 'graphql-tag'
const addNewPlace = (place) => {
return client.mutate({
mutation: gql`
mutation {
addNewPlace(
input: {
$title: String!
}
) {
place { title }
}
}
`,
variables: {
title: place.title
}
})
}
Here I was trying to use variables. When changing the mutation to look like that one below, it is going smoothly however, it's not the right way to do id.
const addNewPlace = (place) => {
return client.mutate({
mutation: gql`
mutation {
addNewPlace(
input: {
title: "${place.title}"
}
) {
place { title }
}
}
`
})
}
Any idea where I made my mistake?
When using variables, there's three steps you need to take:
Add the variables to the request being sent. In Apollo, it's done by specifying a variables property inside the object you pass to mutate. You wrote:
variables: { title: place.title }
This is fine. We are sending some variable, called title, with whatever value, along with the request to our server. At this point, GraphQL doesn't even know about the variables.
Declare your variables inside your operation. You don't have to name your operation, but it's good practice to do so:
mutation AddNewPlace($title: String) {
Here, we are telling GraphQL we've included a variable called title. You could call it anything (foo for example), as long as it matched what you passed to the variables prop in #1. This step is important because A) GraphQL needs to know about the variable and B) it needs to know what type of variable you are passing in.
Finally, include the variable in your mutation, like this:
addNewPlace(input: { title: $title }) {
Be careful not to mix up your variable definition in step #2 with your input definition in step #3. Also, I'm assuming your typeDefs include some kind of input type like AddNewPlaceInput. Rather than passing in just title, you can pass in an object like this:
variables: { input: { title: place.title } }
Then your mutation looks like this:
mutation AddNewPlace($input: AddNewPlaceInput) {
addNewPlace(input: $input) {
# fields returned by the mutation
I would highly recommend enabling a GraphiQL endpoint so you can easily test your queries and mutations before implementing them on the client side.
Lastly, you may want to check and make sure the fields you are asking for in the mutation match your type definition. I'm just guessing here, but if your mutation resolves to a Place type, you wouldn't need to put place { title }, just title, unless your Place type actually has a place field.

Categories