So I'm using Apollo for my app's state, and am so far a little taken aback there's no equivalent to mapStateToProps or something.
As far as I understand, to have any data globally accessible in my store, once I get the data, I need a query to write the data to the store, then another query in my other component to go and get it.
By this point, the other component has very much mounted and rendered, so content just sort of flickers in and out.
In Redux, I can just add new data to the store in my reducers, then anything that's connected with mapStateToProps has access to it.
Is there an equivalent? Or does everything need to go through asynchronous queries? Does anyone else kind of find this an enormous pain?
For example, in one component I'm getting some invitation data:
this.props.client.query({
query: REQUEST_ACTION_DATA,
variables: {
id: actionData.id,
type: actionData.type
}
}).then(data => {
this.props.client.writeQuery({query: GET_ACTION_DATA, data: {
action: {
type: data.data.actionData.type,
object: data.data.actionData.invitation,
__typename: 'ActionDataPayload'
}
}})
this.props.history.push('/auth/register')
})
... then in my other component I have this:
componentWillMount() {
const authToken = localStorage.getItem(AUTH_TOKEN);
if (authToken) {
this.props.history.push('/')
}else{
this.props.client.query({
query: GET_ACTION_DATA
}).then(data => {
if(data.data && data.data.action && data.data.action.type == 'invite'){
this.setState({
invitation: data.data.action.object
})
}
console.log(data)
})
}
}
ignoring the fact that it's hugely unwieldy to write all this for something so simple, is there just a way to access store data without having to wait around?
The graphql higher order component from react-apollo is a supercharged connect from redux. It will do the following:
When the component mounts it will try and execute the query on the Apollo cache. You can think of the query as a DSL for selecting data not only from the server but also from the store! If this works the component is pretty much instantly filled with the data from the store and no remote requests are executed. If the data is not available in the store it triggers a remote request to receive the data. This will then set the loading property to true.
The Apollo cache is a normalised store very similar to your redux store. But thanks to the well thought through limitations of GraphQL the normalisation can happen automatically and you don't have to bother with the structure of the store. This allows the programmer to forget about stores, selectors and requests altogether (unless you are heavily performance optimising your code). Welcome to the declarative beauty of GraphQL frontend frameworks! You declare the data you want and how it gets there is fully transparent.
export default graphql(gql`{ helloWorld }`)(function HelloWorld({ data }) {
if (data.loading) {
return <div>Loading the simple query, please stand by</div>;
}
return <div>Result is: {data.helloWorld}</div>;
});
No selector, no denormalisation happening. This is done by Apollo Client! You will get updated data when the cache updates automatically similar to Redux.
No componentWillMount checks for data is in store already and triggering request actions. Apollo will make the checks and trigger queries.
No normalisation of response data (also done by Apollo Client for you)
If you do any of the above you are probably using Apollo Client wrongly. When you want to optimise your queries start here.
Related
I’ve written an API in GraphQL (Apollo Server), and a separate application in React (Utilizing Apollo Client). The app itself is really straight forward and doesn't require many calls to the API at all, and because of GraphQL, I can get all of the data a user needs in 2 calls and the only time I need to refetch the data is after a mutation.
I have a signin and a signup mutation, then two more mutations for creating an updating the main object a user interacts with.
I'm fairly familiar with React and GraphQL, but I feel like there has to be a way to make one 'larger' call after the signin/signup mutation that fetches all the data user a needs, rather than making a call to each level of nesting based on the loading of a particular component.
Mutations
SignUp
SignIn
CreateShirt
UpdateShirt
Queries
GetShirts
GetDesigns
Ideally, I could utilize a query similar to
query GetUser {
user {
id
email
shirts {
id
style
design {
name
swatch
}
}
}
}
So.. I could return all of this information in the SignIn / SignUp mutations but then after the UpdateShirt mutation, I don't have a (named) query that I can force to refetch. So then I leaned towards just creating a GetUser query that I could refetch but I don't know where to call it from.. it isn't specific to a component necessarily, more to a status of authentication.
What is the most efficient way to query my API for this information? Ignoring the ability to make nested queries and make the components ask for it seems silly.
I think, this is what you are lookin for?
https://www.apollographql.com/docs/react/data/mutations/#refetching-queries
After calling the mutation, this way, you can re-fetch your queries.
// Refetches two queries after mutation completes
const [addTodo, { data, loading, error }] = useMutation(ADD_TODO, {
refetchQueries: [
{query: GET_POST}, // DocumentNode object parsed with gql
'GetComments' // Query name
],
});
However, if you want one request then that means what you are lookin for is Batching.
You can batch multiple queries, something like:
const { data } = useQuery(gql`
query ShameelQuery {
query1
query2
}
`)
You can find more details in their official docs:
https://www.apollographql.com/blog/apollo-client/performance/batching-client-graphql-queries/
I'm looking for a solution to share data across a React-Query mutation without having to create my own internal state or context.
I created a custom hook that takes care of the API call.
myData.ts
const useDataMutation = () => useMutation('MY_DATA_MUTATION', postData);
Then, I use my custom hook in different components.
Component1 is in charge of mutating. The response data will be available in data once the mutate is successful.
Component1.tsx
const { mutate, data } = useDataMutation();
useEffect(() => mutate('some_data'), []);
In another nested component, I want to access the data which came back from the response. But I don't want to pass down the data to 3-4 layers of components. And I wanted to avoid using a Context to access this data.
What I want is something like this:
Component2.tsx
const { data } = useDataMutation();
console.log({ data }); // log data once available.
But in this example, the data from Component2.ts is always undefined.
Is there a simple way to achieve something like this?
Thank you.
at the moment, mutations don't share data across instances like queries do. There is an open issue about it, and contributions are welcome.
If you're using #apollo/client then you can read the previously fetched data directly from the cache.
import Query from './query.ts'
const { todo } = client.readQuery({
query: Query,
variables: {}
})
That will not fetch the data from your server again and instead fetch it from the apollo cache. So you can fetch it using the hook or in the parent component and then 5 levels down you can just pull it again from the cache using the same hook.
When you're using a mutation it will update data in the cache if the properties in the query is the same as before, so if you have a query for getUser and a mutation for updateUser I think the mutation should automatically update the getuser cache data if the data aligns with the mutation data. I'm not sure about this.
I've been reading about the best way to integrate WebSockets into a React/Redux app, and I'm finding answers but with some sentence along the lines of "The best place for websocket implementation is usually middleware."
My question is WHY this is preferred? What is the benefit of doing this vs setting up the websocket/having listeners dispatch actions in the outer App-level React container (in componentWillMount for instance)?
This seems like it would be equivalent in terms of lasting throughout the lifecycle of the app, etc. What am I missing here?
There are couple of pros with placing such logic in middle-wares instead of actual components.
The main reasons in my opinion are:
Each connected component will instantiate a WebSocket or you will
need a global declaration of the connection which will be
independently from the store, in other words, not part of the redux
flow.
Middle-Wares have access to the store and are part of the redux
flow.
You also get access to the entire store, hence you can pass
forward more data then initially dispatched.
You decouple the components from the web-socket logic, hence you can
centralize your code and reuse your components.
All in all, there are no special reasons that are specific regarding using web-sockets with middle-wares, using middle-wares have great advantages in general.
Edit
As a followup to your comment
How would you suggest managing a case where you might want a
particular component to initialize a websocket connection, but want to
manage whether it's already connected, etc... would it be just as
simple as having a flag in the store that says it's connected or is
there a better approach?
As i said, I wouldn't initialize a web-socket connection within a component, rather I would do it in the entry point of my application. like index.js for example.
If your concern is to make sure you won't try to connect when there is already a connection, then a simple socketStart method that get invoked at the point when you create the store and initialize all your App data, you can pass it a callback that will do the rendering and store update through dispatch.
A simple example (keep in mind this is a pseudo code ):
Our Socket-start Method:
export function socketStart(store, callback) {
// this is only a pseudo code!
// register to server -> client events
_socketClient.someFunction = (data) => {
store.dispatch({ type: "Server_Invoked_Some_Function", data });
}
_socketClient.someFunction2 = (data) => {
store.dispatch({ type: "Server_Invoked_Some_Function2", data });
}
// connect to the server via the web-socket client API
_socketClient.connect(() => callback());
}
We can use it in our index.js file:
let store = createStore(
// your reducers here...
// ...
applyMiddleware(socketMiddleware) // our web socket middleware
)
// the callback will invoked only if the connection was successful
// the React-Dom's render function is our callback in this case
socketStart(store, () => { render(<App />, document.getElementById("root")); });
With middleware, you can easily unfold/relay messages between Redux and Web Socket. Also, you can use Redux middleware without React, that means you can write API using Redux on server-side code (probably with Redux saga).
I would agree lifetime management as a React component is easier than a Redux middleware. But if you want to reconnect (destroy/recreate), you will need to use key props to make the reconciler to consider it as a new object, which is a little bit weird.
You can look at redux-websocket-bridge, which unfold Web Socket messages into Redux actions, and relay Redux actions to Web Socket.
On your Web Socket server, you send an action:
ws.on('connection', conn => {
conn.send(JSON.stringify({
type: 'GREETING',
payload: { now: Date.now() }
}));
});
You will get the GREETING action on your Redux reducer. And vice versa, when you want to relay an action to Web Socket, you mark your action with meta.send with true:
this.props.dispatch({
type: 'SIGN_IN',
meta: { send: true },
payload: { authToken: '...' }
});
Let's say at the top of the app, we retrieve some basic information about the app or user before rendering the rest of the application:
const getUser = gql`
query getUser(id: Int!) {
user(id: $id) {
id
name
}
}
`)
function App({ data }) {
return (
<div>
{!data.loading && !data.error && (
// the application
)}
</div>
)
}
export default graphql(getUser, {
options: (props) => ({ variables: { id: props.id }})
})(App)
Now anywhere in the application, it is safe to assume that the user has been loaded and is stored. What is the proper way for another deeply nested component to the retrieve the user data without having to redo the querying and loading logic?
This is the very basic use of a store-based library like Redux. This is not the purpose to guide every step of the way here but you are looking for a single source of truth as described here: http://redux.js.org/docs/introduction/ThreePrinciples.html
In short:
Receiving getUser response should trigger a 'LOGGED_IN' action dispatching user Data, this would be catched by a reducer updating the user object in your store (as much nested as you want), a container would then connect to this user in the store and have all its data using connect()
As of now, I'm not certain there is a proper way, but these are the options I think are reasonable
Manually pass down data via props
Wrap your deeply nested component with the same query
Manual pass down ensures your components rerender correctly, but it can be a pain to refactor. Wrapping your nested component would just hit the cache. Yes, you probably need to redo the loading logic, but that's not a show stopper.
My advice is to manually pass down props for shallow nested components and rewrap deeply nested components. Unfortunately, react-apollo doesn't provide a convenient way to access the apollo-store for nested components the same way that redux's connect container does.
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