I'm using react-apollo as a client to communicate with a GraphQL server that I created. I managed to successfully get subscriptions working with the data.subscribeToMore() function as detailed in the Apollo documentation and the up-to-date data shows up when I run my web application inside of two windows. What I'm trying to do is make it so that an notification alert gets displayed when another client changes data that I'm currently looking at so that I can tell that something changed in case I wasn't paying attention? What would be the correct way of doing this?
update method?
updateQueries method?
The dataFromObjectId and refetchQueries fields did not seem relevant for what I was trying to do. Since I'm using redux, is there a way I could dispatch actions directly from my subscription? Would notification alerts be something that I have to use client.subscribe() with?
Assuming you're using the latest version of Apollo, you should be handing the component a prop named "updateQuery" that contains logic for handling the data.
http://dev.apollodata.com/react/subscriptions.html#subscribe-to-more
This section goes over what you need to do, but essentially your "updateQuery" function should do the following:
Take in an object of structure argumentName.data which contains the new information.
Adds the new object to the results by creating a new object.
Returns the new results object.
so it might look something like this:
(prev, { subscriptionData }) => {
if (!subscription.data) {
//If no new data, return old results
return prev;
}
var newResults = Object.assign(
{},
prev,
queryName: { [subscriptionData.data, ...prev[queryName]] }
);
return newResults;
Related
I need to pass a couple of arguments in a react query one of which needs to decided by the user action
Here is how the query looks so far:
const { refetch: likeDislikeProfile } = useQuery(
['like_dislike_profile'],
() => like_dislike_profile_q(data.userid, <BOOLEAN_ARG>), // 👈
{ enabled: false }
)
Whenever the clicks on a like/dislike button, the argument will be true/false respectively.
This is further used as a query param in the request : action?like=false
How do I achieve this?
My approach
create a local state that changes on button click
create a side effect (useEffect) method which is triggered when this state changes
which will further trigger this react query
This approach seems bad, can't think of anything else atm
Looks like your HTTP request is changing data in the backend, that's the use case for mutations.
From the official docs
A query is a declarative dependency on an asynchronous source of data that is tied to a unique key. A query can be used with any Promise based method (including GET and POST methods) to fetch data from a server. If your method modifies data on the server, we recommend using Mutations instead.
For your use case it should be something like this
const updateLike = useMutation((id, bool) => like_dislike_profile_q(id, bool))
// invoke the mutation at any point like this
updateLike.mutate('my-id', true)
Read more about mutations on Tkdodo's blog post on mutations
I am considering using React Relay or the Apollo Client. I like the idea of GraphQL as a query language that can be executed against any API or data store.
However, I am surprised by the need to manually (and imperatively) update the store/cache after a simple mutation such as adding a todo item to a list. Here is the documentation for the updater/update functions:
http://facebook.github.io/relay/docs/en/mutations.html
https://www.apollographql.com/docs/react/essentials/mutations.html
Why is a user-defined updater function required?
Specifically, why must I write an updater function to handle running these two GraphQL queries in sequence?
//Create todo item
mutation createTodo($input: TodoInput!) {
createTodo(input: $input) {
id
name
desc
}
}
mutationVariables = { input: { name: 'Shopping', desc: 'Get groceries' }};
//Select all todos
query {
todos {
id
name
desc
}
}
Why would the todos query not return the new todo item automatically? That is the POINT of a declarative query language IMO.
The idea behind updating the local cache is to minimize the amount of data being passed between the client and the server.
Updating the local cache manually is entirely up to you. You can absolutely program your mutation to return the new or all todo's then it will update your local cache. (it will be slower than manually updating since you will need to wait for the response.) There are some good use cases for manually updating vs waiting for the response from the server
With apollo you can also turn off local cache and just use "network-only" as your fetch policy.
I know Redux solves this but I came up with an idea.
Imagine I have an app that gets some JSON on start. Based on this JSON I'm setting up the environment, so let's assume the app starts and it downloads an array of list items.
Of course as I'm not using Redux (the app itself is quite simple and Redux feels like a huge overkill here) if I want to use these list items outside of my component I have to pass them down as props and then pass them as props again as deep as I want to use them.
Why can't I do something like this:
fetch(listItems)
.then(response => response.json())
.then(json => {
window.consts = json.list;
This way I can access my list anywhere in my app and even outside of React. Is it considered an anti-pattern? Of course the list items WON'T be changed EVER, so there is no interaction or change of state.
What I usually do when I have some static (but requested via API) data is a little service that acts kind like a global but is under a regular import:
// get-timezones.js
import { get } from '../services/request'
let fetching = false
let timez = null
export default () => {
// if we already got timezones, return it
if (timez) {
return new Promise((resolve) => resolve(timez))
}
// if we already fired a request, return its promise
if (fetching) {
return fetching
}
// first run, return request promise
// and populate timezones for caching
fetching = get('timezones').then((data) => {
timez = data
return timez
})
return fetching
}
And then in the view react component:
// some-view.js
getTimezones().then((timezones) => {
this.setState({ timezones })
})
This works in a way it will always return a promise but the first time it is called it will do the request to the API and get the data. Subsequent requests will use a cached variable (kinda like a global).
Your approach may have a few issues:
If react renders before this window.consts is populated you won't
be able to access it, react won't know it should re-render.
You seem to be doing this request even when the data won't be used.
The only downside of my approach is setting state asynchronously, it may lead to errors if the component is not mounted anymore.
From the React point of view:
You can pass the list from top level via Context and you can see docs here.
Sample of using it is simple and exists in many libraries, such as Material UI components using it to inject theme across all components.
From engineering concept of everything is a trade of:
If you feel that it's gonna take so much time, and you are not going to change it ever, so keep it simple, set it to window and document it. (For your self to not forget it and letting other people know why you did this.)
If you're absolutely certain they won't ever change, I think it's quite ok to store them in a global, especially if you need to access the data outside of React. You may want to use a different name, maybe something like "appNameConfig"..
Otherwise, React has a feature called Context, which can also be used for "deep provision" - Reference
For example I have two components - ListOfGroupsPage and GroupPage.
In ListOfGroupsPage I load list of groups from the server and store it to the state.groups
In route I have mapping like ‘group/:id’ for GroupPage
When this address is loaded, the app shows GroupPage, and here I get the data for group from state.groups (try to find group in state via id).
All works fine.
But if I reload page, I'm still on page /group/2, so GroupPage is shown. But state is empty, so the app can't find the group.
What is the proper way to load data in React + Redux? I can see this ways:
1) Load all data in root component. It will be very big overhead from traffic side
2) Don't rely on store, try to load required data on each component. It's more safe way. But I don't think that load the same data for each component - it's cool idea. Then we don't need the state - because each component will fetch the data from server
3) ??? Probably add some kind of checking in each component - first try to find required data in store. If can't - load from the server. But it requires much of logic in each component.
So, is there the best solution to fetch data from server in case of usage Redux + ReactJS?
One approach to this is to use redux-thunk to check if the data exist in the redux store and if not, send a server request to load the missing info.
Your GroupPage component will look something like
class GroupPage extends Component {
componentWillMount() {
const groupId = this.props.params.groupId
this.props.loadGroupPage(groupId);
}
...
}
And in your action...
const loadGroupPage = (groupId) => (dispatch, getState) => {
// check if data is in redux store
// assuming your state.groups is object with ids as property
const {
groups: {
[groupId]: groupPageData = false
}
} = getState();
if (!groupPageData) {
//fetch data from the server
dispatch(...)
}
}
I recommend caching the information on the client using localstorage. Persist your Redux state, or important parts of it, to localstorage on state change, and check for existing records in localstorage on load. Since the data would be on the client, it would be simple and quick to retrieve.
The way I approach this is to fetch from the server straight after the store has been created. I do this by dispatching actions. I also use thunks to set isFetching = true upon a *_REQUEST and set that back to false after a *_SUCCESS or *_FAILURE. This allows me to display the user things like a progress bar or spinner. I think you're probably overestimating the 'traffic' issue because it will be executed asynchronosly as long as you structure your components in a way that won't break if that particular part of the store is empty.
The issue you're seeing of "can't get groups of undefined" (you mentioned in a comment) is probably because you've got an object and are doing .groups on it. That object is most likely empty because it hasn't been populated. There are couple of things to consider here:
Using ternary operators in your components to check that someObject.groups isn't null; or
Detailing in the initialState for someObject.groups to be an empty array. That way if you were to do .map it would not error.
Use selectors to retrieve the list of groups and if someObject.groups is null return an empty array.
You can see an example of how I did this in a small test app. Have a look at specifically:
/src/index.js for the initial dispatch
/src/redux/modules/characters.js for the use of thunks
/src/redux/selectors/characters.js for the population of the comics, series, etc. which are used in the CharacterDetails component
As a preface, I'm still new to React, so I'm still fumbling my way through things.
What I have is a component that fetches data to render an HTML table. So I call my Actions' fetchData() (which uses the browser's fetch() API) from within componentWillMount(), which also has a listener for a Store change. This all works well and good, and I'm able to retrieve and render data.
Now the next step. I want to be able to fetch new data when the component's props is updated. But I'm not exactly sure what the proper way to do so is. So I have a three part question
Would the proper place to do my fetchData() on new props be in componentWillReceiveProps(), after validating that the props did change, of course?
My API is rather slow, so it's entirely possible a new prop comes in while a fetch is still running. Is it possible to cancel the old fetch and start a new one, or at least implement logic to ignore the original result and wait for the results from the newer fetch?
Related to the above question, is there a way to ensure only one fetch is running at any time besides having something like an isLoading boolean in my Action's state (or elsewhere)?
Yes, componentWillReceiveProps is the proper place to do that.
Regarding point 2 and 3:
The idea of cancelling the task and maintaining 'one fetch running' seems to be inadequate. I don't think this kind of solution should be used in any system because implementation would limit an efficiency of your app by design.
Is it possible to cancel the old fetch and start a new one, or at least implement logic to ignore the original result and wait for the results from the newer fetch?
Why don't you let a 'newer fetch' response override an 'old fetch' response?
If you really want to avoid displaying the old response you can implement it simply using a counter of all fetchData calls. You can implement it in this way:
var ApiClient = {
processing: 0,
fetchData: function(){
processing++
return yourLibForHTTPCall.get('http://endpoint').then(function (response)){
processing--
return response
}
},
isIdle: function(){
return processing == 0
}
}
and the place where you actually make a call:
apiClient.fetchData(function(response){
if(apiClient.isIdle()){
this.setState({
})
}
}
I hope yourLibForHTTPCall.get returns a Promise in your case.