2

I've created a LoginMutation which return a token and a user (with his id and firstName). Here is the mutation schema :

const LOGIN_MUTATION = gql`
  mutation loginMutation($email: String!, $password: String!) {
    loginUser(email: $email, password: $password) {
      token
      user {
        id
        firstName
      }
    }
  }

When I enter on my website the token and the user are well returned by the graphql server. The user is stored and I can see it in my dev tools :

enter image description here

I have created a Layout component and I want to display on it the firstName of the user. So how can I get the data from the apollo store ?

Thanks for your help.

Below are provided the files concerning by this issue :

LoginPage.js

class LoginPage extends Component {

constructor(props) {
    super(props);
    this.state = {
        login: true, //switch between Login and SignUp
        email: '',
        password: '',
        firstName: '',
        lastName: '',
        loading: false,
        error: ''
    };
    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
}

handleSubmit(){
    this.setState({loading: true, error: ''});
    this._confirm();
}

handleInputChange(event) {
    const target = event.target;
    const value = target.value;
    const name = target.name;

    this.setState({
      [name]: value
    });
  }

render(){

    return (
            <div>
                <div>
                    {this.state.loading ? 
                    <CircularProgress size={60} thickness={7} /> :
                    this.state.login ? 
                        <LoginForm onSubmit={this.handleSubmit} onChange={this.handleInputChange}/> 
                        : 
                        <RegisterForm />
                    }
                </div>
                {this.state.error ? <div className="error">{this.state.error}</div> : ''}
                <a
                    onClick={() => this.setState({ login: !this.state.login })}
                >
                {this.state.loading ? 
                '' : this.state.login ? 
                        'Besoin d\'un compte ?' : 'Déjà un compte ?'
                }
                </a>
            </div>
    )
}

_confirm = ()  => {
  const { firstName, lastName, email, password } = this.state;
  if (this.state.login) {
    this.props.loginMutation({
      variables: {
        email,
        password,
      }
    })
    .then(({data}) => {
      this.setState({loading: false});
      const { token } = data.loginUser;
      this._saveUserData(token);
      checkAuth.authenticate();
    })
    .then(() => {
      this.props.history.push(`/`);
    }).catch((error) => {
      this.setState({loading: false, error: error});
    });
  }
}

   _saveUserData = (token) => {
    localStorage.setItem('token', token);
  }
}   

const LOGIN_MUTATION = gql`
    mutation loginMutation($email: String!, $password: String!) {
    loginUser(email: $email, password: $password) {
       token
       user {
        id
        firstName
       }
    }
 }
`

export default compose(graphql(LOGIN_MUTATION, { name: 'loginMutation' }))(LoginPage)

App.js which is the router between pages

class App extends Component {
  constructor(props) {
      super(props);
  }

  render() {
    return (
      <div>
        <Switch>
          <Route exact path='/connexion' component={LoginPage} />
          <PrivateRoute exact path='/' component={WelcomePage} />
        </Switch>
      </div>
    )
  }
}

export default App;

Layout.js where I want to get the user firstName from the cache to pass it on Sidebar props

class Layout extends Component {
    constructor(props) {
        super(props);
        this.state = {
            open: false,
        };
        this.logout = this.logout.bind(this);
    }

    logout() {
        this.props.client.resetStore();
        localStorage.removeItem('token');
        checkAuth.signout();
        this.props.history.push(`/`);
    }

    handleTouchMap() {
        this.setState({open: !this.state.open});
    }

    render() {
        return (
            <div>
                <AppBar title="myApp" iconElementRight={<RightMenu onDisconnect={ this.logout } />} onLeftIconButtonTouchTap = { this.handleTouchMap.bind(this) } />
                <Sidebar open={this.state.open} onRequestChange={(open) => this.setState({open})} firstName={this.props.firstName} />
                { this.props.children }
            </div>
        );
    }

}

export default withApollo(withRouter(Layout));

WelcomePage.js

class WelcomePage extends Component {
    render() {
        return (
            <div>
                <Layout>
                    <WelcomeComponent />
                </Layout>
            </div>
        );
    }
}

export default WelcomePage;
Putxe
  • 1,054
  • 1
  • 16
  • 23
  • you will get clear picture on this link https://www.apollographql.com/docs/react/basics/mutations.html – Javed Shaikh Feb 23 '18 at 09:24
  • Thank you for your response javed, I've read it and one sentence is very interesting in that case "One good strategy can be to simply ask for any fields that might have been affected by the mutation." but there is no more concrete explanation. In my case my LoginPage component run the mutation and then I push the router to a page which want to display the data from apollo cache. There is no link between us so I can't pass props from one to another. Do you have any idea or example that can solve that issue ? – Putxe Feb 23 '18 at 09:49
  • can you plz provide your code? – Javed Shaikh Feb 23 '18 at 09:54
  • Yes sorry I have edited my post with the code – Putxe Feb 23 '18 at 10:02
  • try readQuery in Layout Component it fetch data from local cache. https://www.apollographql.com/docs/react/basics/caching.html – Javed Shaikh Feb 23 '18 at 10:15
  • The problem is that readQuery only fetch data from existing query and in my case it's a mutation, I will try readFragment : https://www.apollographql.com/docs/react/basics/caching.html#readfragment thank you – Putxe Feb 23 '18 at 10:25
  • Did it work out with readFragment? Before I would use a query like the answer below, but your your question got me wondering if you can do it without too – Vialito Feb 24 '18 at 10:43
  • It didn't work with readFragment. I tried to readFragment on the client so I used "withApollo" and I wrote the fragment on the component render function. There is no error detected so it's just an issue of how retrieve the data from the fragment but for the moment I don't know how to achieve that. – Putxe Feb 26 '18 at 08:55
  • Hey Vincent, here is an exemple on how to use readFragment to display user informations : https://stackoverflow.com/questions/49025250/how-to-use-data-from-readfragment-in-apollo/49178449#49178449 – Putxe Mar 08 '18 at 16:56

1 Answers1

5

There are 2 options. First I'll explain the solution I prefer which is quite simple, and later the simpler solution.

First, implement a basic query

In your case it would be something like:

const CURRENT_USER_QUERY = gql`
  query currentUserQuery {  
    user {
        id
        firstName
      }
   }`;

And you would add it like this to the Layout component:

export default compose(
  withApollo,
  graphql(CURRENT_USER_QUERY, { /* ... query configuration */ })
)(withRouter(Layout));

Note that one of the query options is the fetchPolicy. In this specific scenario you might only need cache-only. It should be enough for a start, but as you add more fields you might want to consider changing it to something more appropriate to your design. Here you can read about Query Fetch Policies

Now this query still won't retrieve the data, since it isn't stored as expected by the query. That leads to the second part:

Second, update the cache after the mutation

To do that you will need to use the update option with your mutation operation.

In your case, the mutation operation should look something like:

graphql(LOGIN_MUTATION, { name: 'loginMutation',
  update: (proxy, { data: { loginUser } }) => {      
    const data = { user: loginUser.user };
    proxy.writeQuery({ query: CURRENT_USER_QUERY, data });
  }
})

If you've seen the examples in the documentation you can see that there is no call to proxy.readQuery here, for 2 reasons.

  • It's safe to assume the user is null in this login case. It might not be with other mutations.
  • If the object you are trying to read is not in the cache, proxy.readQuery will throw an exception.

The Simpler solution

It requires you only to add a basic query.

For example:

const USER_QUERY = gql`
  query userQuery($userId: ID) {
    user(id: $userId) {
        id
        firstName
      }
   }`;

// ...

export default compose(
  withApollo,
  graphql(USER_QUERY, { 
      variables: () => { 
        return { userId: /* get the user id, maybe from the local storage*/}; 
      }, 
      /* ... query configuration */ 
  }),
)(withRouter(Layout));

The draw back, as you can see, is that you'll always need to store and provide the user id to get the data of your current user. That could be cumbersome when you find you need access to the user's data in other places.

Tal Z
  • 3,170
  • 17
  • 30
  • Your solution is working perfectly and your explanation is very detailed, you saved me a lot of time and now I understand better how Apollo is working. So thanks a lot Tal Z ! – Putxe Feb 26 '18 at 08:58
  • In fact the simpler solution is working but not the first. I have the following error "Cannot query field user on type Query" rendered by Layout component. It seems like the store is not updated by proxy.writeQuery in loginMutation... – Putxe Feb 26 '18 at 14:58
  • I'm actually using something very similar to the solution I described in my project. So I'm not sure what's different in your case, it might need a bit more work to fit your code. This error sounds to me like it might be related to the `FetchPolicy`. But anyway, I'm glad at least one solution works for you. – Tal Z Feb 26 '18 at 21:18
  • Yes you're right I will look on that, thanks again for providing me great explanations to solve my problem ! – Putxe Feb 28 '18 at 08:36
  • Some of the links referenced are broken. Here are the new ones: fetch policy (https://www.apollographql.com/docs/tutorial/queries/#customizing-the-fetch-policy) and updating cache after a mutation (https://www.apollographql.com/docs/react/data/mutations/#updating-the-cache-after-a-mutation) – keyserfaty Oct 15 '19 at 11:45