I am using Apollo 2.0 to manage my graphQL API calls and to handle the global state of my react application.
I am trying to create a login screen where a user enters their username and password, this gets sent to my API to authenticate and upon success, I want to then set the global state of isLoggedIn
to true
.
So far, I am able to set the global state with one mutation which utilises the @client
declaration so it is only concerned with local state. I have another mutation which makes the graphQL API call and validates username / password and then returns success / error responses.
I want to be able to set isLoggedIn
once the API call mutation has completed or failed.
My client has the following default state and resolvers set like so:
const httpLink = new HttpLink({
uri: '/graphql',
credentials: 'same-origin'
});
const cache = new InMemoryCache();
const stateLink = withClientState({
cache,
resolvers: {
Mutation: {
updateLoggedInStatus: (_, { isLoggedIn }, { cache }) => {
const data = {
loggedInStatus: {
__typename: 'LoggedInStatus',
isLoggedIn
},
};
cache.writeData({ data });
return null;
},
},
},
defaults: {
loggedInStatus: {
__typename: 'LoggedInStatus',
isLoggedIn: false,
},
},
});
const link = ApolloLink.from([stateLink, httpLink])
const client = new ApolloClient({
link,
cache
});
export default client
Then in my Login component I have the following mutations and queries which I pass as a HOC with the help of compose:
const UPDATE_LOGGED_IN_STATUS = gql`
mutation updateLoggedInStatus($isLoggedIn: Boolean) {
updateLoggedInStatus(isLoggedIn: $isLoggedIn) @client
}`
const AUTHENTICATE = gql`
mutation authenticate($username: String!, $password: String!) {
auth(username: $username, password: $password) {
username
sales_channel
full_name
roles
}
}`
const GET_AUTH_STATUS = gql`
query {
loggedInStatus @client {
isLoggedIn
}
}`
export default compose(
graphql(GET_AUTH_STATUS, {
props: ({ data: { loading, error, loggedInStatus } }) => {
if (loading) {
return { loading };
}
if (error) {
return { error };
}
return {
loading: false,
loggedInStatus
};
},
}),
graphql(UPDATE_LOGGED_IN_STATUS, {
props: ({ mutate }) => ({
updateLoggedInStatus: isLoggedIn => mutate({ variables: { isLoggedIn } }),
}),
}),
graphql(AUTHENTICATE, {
props: ({ mutate }) => ({
authenticate: (username, password) => mutate({ variables: { username, password } }),
}),
})
)(withRouter(Login));
So as you can see I have this.props.authenticate(username, password)
which is used when the login form is submitted.
Then I have the this.props.updateLoggedInStatus(Boolean)
which I am able to update the client cache / state.
How do I combine these so that I can call authenticate()
and if it's successful, set the loggedInStatus
and if it fails, set a hasErrored
or errorMessage
flag of sorts?
Thanks in advance.
EDIT:
I have attempted to handle updating the state within the callback of my mutation.
// Form submission handler
onSubmit = async ({ username, password }) => {
this.setState({loading: true})
this.props.authenticate(username, password)
.then(res => {
this.setState({loading: false})
this.props.updateLoggedInStatus(true)
})
.catch(err => {
this.setState({loading: false, errorMessage: err.message})
console.log('err', err)
})
}
Is there a better way of doing it than this? It feels very convoluted having to wait for the call back. I would have thought I could map the response to my cache object via my resolver?