Contact Me Part II — The Client
Build a frontend app with React, Apollo and GraphQL
Overview:
This is the second part of this tutorial, which makes use of the GraphQL server we made in the first part. If you just landed here, check out the first part on how to build the GraphQL server for the Contact Me app. In this part of the tutorial, I will help you build a user interface using React, where a user can type their email address and the message they want to send us. We will use the Apollo client to send a mutation to our GraphQL server which processes the message and sends us an email containing the email address of the sender and the message they sent. Finally, I will help you deploy your app using Surge.
Setup:
We are going to use the create-react-app
cli tool to setup our frontend app. If you don’t have the cli tool yet, you can execute the following command to install it globally in your system using npm
:
npm install -g create-react-app
Then, let’s go to our projects folder:
cd messageMe
Here, we will use the create-react-app
command to create a new app and call it client
.
create-react-app client
After the process is complete, you should have a new folder named client
with the basics of a React app all setup and ready to run. Next, let’s go into the new folder and start the development server:
cd client
yarn start
This should open a new page in your browser at localhost:3000
with the default React welcome page.
App.js
First thing we need to do is create a new folder named components
in the src
folder. This is where we will create and keep all our view components. Inside the folder, let’s create a new file named Main.jsx
which is going to be our parent component in our component hierarchy.
import React, { Component } from 'react';
class Main extends Component {
render() {
return (
<div>
Hello World!
</div>
);
}
}
export default Main;
Next, let’s remove all the default code from your App.js and render the Main component.
import React, { Component } from 'react';
import Main from './components/Main';
const App = () => <Main />;
export default App;
Material-UI
Material-UI is a set of React components that implement Google’s material design. I will help you build the form using material-ui
components. First thing we need to do is install it in our app. Additionally, let’s install bootstrap
, primarily for structuring our content.
yarn add material-ui bootstrap
Next, let’s wrap the Main
component with the MuiThemeProvider
module that we can import from the material-ui
package. Also, don’t forget to import bootstrap
from the node modules that we just installed.
import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import Main from './components/Main';
const App = () => (
<MuiThemeProvider>
<Main />
</MuiThemeProvider>
);
export default App;
Doing this let’s us import UI components in any of our components from the material-ui
module.
Main.jsx:
Next, let’s create UI components for our app. You will need three basic components to build the contact form.
- Email Form
- Message Form
- Send Button
Let’s build each component in a separate file for better modularization.
EmailForm Component:
Let’s create a new file named EmailForm.jsx
in the src
folder.
import React from 'react';
const EmailForm = ({}) => {
return (
<div>
EmailForm
</div>
);
}
export default EmailForm;
Next, let’s import this component in our Main
component and render it. We will later need to perform API requests to our GraphQL server form the Main
component, which means that we need to store our input data in the Main
component. We can use the component’s state to store the input data, starting with the user’s email. One last thing, I am going to add the text-center
class from bootstrap
which will align everything to the center, giving us a neater UI.
import React, { Component } from 'react';
import EmailForm from './EmailForm';
class Main extends Component {
state = {
email: ''
}
render() {
return (
<div className="text-center">
<EmailForm
email={this.state.email}
onEmailChange={this.onEmailChange}
/>
</div>
);
}
}
export default Main;
Next, let’s edit the EmailForm
component. We are passing two props to it, which we need to receive in the EmailForm
component. Here, let’s add the TextField UI component from material-ui
. For the TextField
, we will use the following props:
value
: Which is going to be the email value that we receive as props.floatingLabel
: Which is going to be our placeholder value to prompt the user.onChange
: Which needs a function name to handle changes in theTextField
.
Let’s work backwards to write the onChange
function by first receiving it as props.
import React from 'react';
import TextField from 'material-ui/TextField';
const EmailForm = ({ email, onEmailChange }) => {
return (
<TextField
value={email}
floatingLabelText="Enter your email:"
onChange={onEmailChange}
/>
);
}
export default EmailForm;
Next, let’s create the onEmailChange
function in our Main
component. The function, by default, will receive the event
and the newValue
as its arguments. We can use the newValue
to update the state value for email
.
import React, { Component } from 'react';
import EmailForm from './EmailForm';
class Main extends Component {
state = {
email: ''
}
onEmailChange = (event, newValue) => this.setState({ email: newValue });
render() {
return (
<div className="text-center">
<EmailForm
email={this.state.email}
onEmailChange={this.onEmailChange}
/>
</div>
);
}
}
export default Main;
Now, when you type your email in the text field, the state value updates accordingly. We are primarily focusing on creating the basics with this tutorial, meaning there are a lot of additional security features you can implement to validate the email.
MessageForm Component:
Now that we have the email
value, we need to add the message
value. The process will be similar with small changes in the TextField
. First, let’s create a new file named MessageForm.jsx
. Here, let’s add additional props in the TextField
to support multiLine
. I added the floatingLabelStyle
to prevent the display style to change after adding multiLine
.
import React from 'react';
import TextField from 'material-ui/TextField';
const MessageForm = ({ message, onMessageChange }) => {
return (
<div>
<TextField
value={message}
multiLine={true}
floatingLabelText="Say hi!"
floatingLabelStyle={{ left: 0 }}
onChange={onMessageChange}
/>
</div>
);
}
export default MessageForm;
Next, let’s work backwards and plug the MessageForm
component to our Main
component. First, let’s import the component in the Main
component to render it. Then, let’s add a state variable that will store the message
value. Finally, let’s write the onMessageChange
handler to update the state when the message is being typed.
import React, { Component } from 'react';
import EmailForm from './EmailForm';
import MessageForm from './MessageForm';
class Main extends Component {
state = {
email: '',
message: ''
}
onEmailChange = (event, newValue) => this.setState({ email: newValue });
onMessageChange = (event, newValue) => this.setState({ message: newValue });
render() {
return (
<div className="text-center">
<EmailForm
email={this.state.email}
onEmailChange={this.onEmailChange}
/>
<MessageForm
message={this.state.message}
onMessageChange={this.onMessageChange}
/>
</div>
);
}
}
export default Main;
Now that we have all the data that we need, let’s add the SendButton
component.
SendButton Component:
The SendButton
component should display a button and call the sendMessage
function, which should be defined in the Main
component. Let’s use the FlatButton
component from the material-ui
module to create the button.
import React from 'react';
import FlatButton from 'material-ui/FlatButton';
const SendButton = ({ sendMessage }) => {
return (
<FlatButton onClick={sendMessage}>
Send Message
</FlatButton>
);
}
export default SendButton;
Next, let’s plug in the SendButton
component to our Main
component and write the sendMessage
function. For now, it doesn’t have to do anything.
import React, { Component } from 'react';
import EmailForm from './EmailForm';
import MessageForm from './MessageForm';
import SendButton from './SendButton';
class Main extends Component {
state = {
email: '',
message: ''
}
onEmailChange = (event, newValue) => this.setState({ email: newValue });
onMessageChange = (event, newValue) => this.setState({ message: newValue });
sendMessage = () => {
console.log('sendMessage');
}
render() {
return (
<div className="text-center">
<EmailForm
email={this.state.email}
onEmailChange={this.onEmailChange}
/>
<MessageForm
message={this.state.message}
onMessageChange={this.onMessageChange}
/>
<SendButton
sendMessage={this.sendMessage}
/>
</div>
);
}
}
export default Main;
Now that all our views are in place and fully functional the next thing we need to do is connect our app with the GraphQL server we built and send a mutation request to the server.
Apollo:
Since we are using React, there are specific packages that are required to run Apollo and React together. Most of the related information can be found here. First, let’s install all the packages we need with the following command.
yarn add apollo-client-preset react-apollo graphql-tag graphql
Let’s look at each package individually to see why we need them:
apollo-client-preset
: Contains theApolloClient
component and all necessary modules to instantiate an Apollo client to connect it to our GraphQL server.react-apollo
: Contains theApolloProvider
component which helps us use Apollo anywhere in our React component hierarchy.graphql-tag
: Parses all our GraphQL queries and mutations so that our GraphQL server can understand it.graphql
: All the modules above are dependent on this core module, which makes using GraphQL possible in our React components.
Now that we have all the necessary packages for Apollo, let’s make use of the ApolloClient
and the ApolloProvider
.
ApolloClient:
In our App.js
, let’s create a new client instance by adding the following code. You should use the HttpLink
instance to add your deployed GraphQL server running on Heroku.
import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import Main from './components/Main';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://YOUR_GRAPHQL_SERVER.herokuapp.com/graphql' }),
cache: new InMemoryCache()
});
const App = () => (
<MuiThemeProvider>
<Main />
</MuiThemeProvider>
);
export default App;
ApolloProvider:
Next, let’s import the ApolloProvider
component from the react-apollo
package and wrap our Main
component with it. The ApolloProvider
component requires the client we just created as it’s props.
import React, { Component } from 'react';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
import Main from './components/Main';
import { ApolloProvider } from 'react-apollo';
import { ApolloClient } from 'apollo-client';
import { HttpLink } from 'apollo-link-http';
import { InMemoryCache } from 'apollo-cache-inmemory';
const client = new ApolloClient({
link: new HttpLink({ uri: 'https://YOUR_GRAPHQL_SERVER.herokuapp.com/graphql' }),
cache: new InMemoryCache()
});
const App = () => (
<ApolloProvider client={client}>
<MuiThemeProvider>
<Main />
</MuiThemeProvider>
</ApolloProvider>
);
export default App;
We have now hooked Apollo to our React component hierarchy. Next, let’s send mutations from our components. So exciting!!!
Sending Mutations:
To send mutations, let’s add two more packages in our Main
component.
import { graphql } from 'react-apollo';
import gql from 'graphql-tag';
We will use the graphql
module to wrap our Main
component which will connect React and Apollo. The GraphQL method requires GraphQL queries as its arguments. The queries should be wrapped with the gql
module from the graphql-tag
package.
const sendMessage = gql`
mutation($email: String!, $message: String!) {
createMessage(
email: $email,
message: $message
) {
email
message
}
}
`;
export default graphql(sendMessage)(Main);
Notice that the mutation inside our gql
tag is the same as the one we experimented with using the GraphQL Playground in the first part of the tutorial. Here, we are also adding variables in our mutations. Wrapping our component with graphql
gives us access to GraphQL functions through props. Therefore, we can call this.props.mutate()
, which we can also use to pass in the query variables. Next, let’s update the sendMessage
method inside our component class to send the mutation when we click the SendButton
.
sendMessage = () => {
const { email, message } = this.state;
this.props.mutate({
variables: { email, message }
})
.then(data => {
this.setState({ email: '', message: '' });
})
.catch(error => console.log('error:', error));
}
After your server finishes sending the email, we should get back the data that we send from our server. Right now, our server doesn’t return anything useful, but we can take this opportunity to update the then()
method to reset our state variables. This will clear the form after the user sends a message. If everything works well, when you press the SendButton
, you should get your email. How cool is that?
Surge:
Finally, we are going to build this app for production. If you don’t have surge installed on your system, you can get it from npm
using the following command:
npm install -g surge
Next, let’s run the build command to build the production version of our app:
yarn build
Then, let’s go into the build folder and run the following command:
surge
You will have to sign up and/or sign in if you haven’t yet. You can choose a domain name if you want to. I am going to go with the incongruous default domain name surge gives me. After the process completes, open the link in your browser and test it. Awesome right? Now you can share what you just built.
In a nutshell:
We now have a fully functional app that let’s our users send us messages directly to our inboxes. Checkout my version here and please let me know how I could have done it better. This app is complete, but you can still add a lot more features including email verification, error feedback for users or even profanity filter for your messages. Don’t hesitate to email me your deployed version for feedbacks or if you have any questions using the app above ;).
Happy Coding!