2
votes

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;
1
you will get clear picture on this link apollographql.com/docs/react/basics/mutations.htmlJaved Shaikh
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
can you plz provide your code?Javed Shaikh
Yes sorry I have edited my post with the codePutxe
try readQuery in Layout Component it fetch data from local cache. apollographql.com/docs/react/basics/caching.htmlJaved Shaikh

1 Answers

5
votes

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.