Creating a React Native/Redux application for searching posts on Medium

Setting up

Let’s create a new React Native project with the following command:

create-react-native-app medium-searcher
Important Note: If npm ask you to update the project to React Native 0.56.0, don’t do it. Until they fix the issue if you update to the latest version it breaks the project entirely.

After that let’s install some necessary dependencies:

npm install --save redux react-redux

Basic Layout

Before coding let’s start our emulator with the following commands:

npm start   // after the QR code appears press one of the following
i // for ios emulator
a // for android emulator

Now that your emulator is running let’s make a basic bootstrap of our app and will continue building from there. Create a new folder on the root of the project called components. I personally prefer to create a separate folder for each component and their styles. So go ahead and create the following files:

components
|--- SearchBar
|--- SearchBar.js
|--- SearchBarStyles.js
|--- SearchResults
|--- SearchResults.js
|--- SearchResultsStyles.js

with the following content:

// SearchResults.js
import React, {Component} from 'react';
import {Text} from 'react-native';
export default class SearchResults extends Component {
render() {
return(
<Text>SearchResults</Text>
)
}
}
// SearchBar.js
import React, {Component} from 'react';
import {Text} from 'react-native';
export default class SearchBar extends Component {
render() {
return(
<Text>SearchBar</Text>
)
}
}
// App.js
import React from 'react';
import { StyleSheet, View } from 'react-native';
import SearchBar from './components/SearchBar/SearchBar';
import SearchResults from './components/SearchResults/SearchResults';

export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<SearchBar/>
<SearchResults/>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});

As you can see we just added some basic text as placeholders for the content of each component and modified App.js to import both components and render them on the main page. You should see something like this in your emulator:

Initial components rendered on the main page

Components Layout

Our SearchBar component will be composed by a TextInput and Button with the search icon that will trigger the action to search on Medium for articles.

For the icon of the Search button we are going to use the default Icon’s library of Expo:

And here you can browse the Icon’s sets:

For the SearchResults component we will have one line for each result that once clicked will redirect us to the article.

The implemented code on each file at this moment should look something like this:

// App.js
import React from 'react';
import { StyleSheet, View } from 'react-native';

import SearchBar from './components/SearchBar/SearchBar';
import SearchResults from './components/SearchResults/SearchResults';

export default class App extends React.Component {
render() {
return (
<View style={styles.container}>
<SearchBar/>
<SearchResults/>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
});
// SearchBar.js
import React, {Component} from 'react';
import {TextInput, TouchableOpacity, View} from 'react-native';
import { FontAwesome } from '@expo/vector-icons';

const styles = require('./SearchBarStyles');

export default class SearchBar extends Component {
render() {
return(

<View style={styles.searchBarContainer}>
<TextInput
placeholder = 'Enter your search terms'
style = {styles.textInputSearch}
underlineColorAndroid={'transparent'}
/>
<TouchableOpacity
style = {styles.textSearchButton}
>
<FontAwesome name="search" size={16} color="#000" />
</TouchableOpacity>
</View>
)
}
}
// SearchBarStyles.js
const React = require('react-native');
const { Dimensions, StyleSheet } = React;

module.exports = StyleSheet.create({
searchBarContainer: {
width: Dimensions.get('window').width - 20,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
height: 50,
borderBottomWidth: 2,
marginVertical: 10,
borderColor: 'lightgray',
flex: 1
},
textInputSearch: {
flex: 8,
borderColor: 'lightgray',
borderWidth: 1,
borderRadius: 5,
marginRight: 10,
height: 40,
paddingLeft: 10
},
textSearchButton: {
flex: 1,
backgroundColor: 'lightgray',
borderRadius: 5,
alignItems: 'center',
justifyContent: 'center',
padding: 10,
height: 40
}
})
// SearchResults.js
import React, {Component} from 'react';
import {View, TouchableOpacity, Text} from 'react-native';

const styles = require('./SearchResultsStyles');

export default class SearchResults extends Component {
render() {
return(
<View style={styles.searchResultsContainer}>
<TouchableOpacity>
<Text>
Result 1
</Text>
</TouchableOpacity>
<TouchableOpacity>
<Text>
Result 2
</Text>
</TouchableOpacity>
</View>
)
}
}
// SearchResultsStyles.js
const React = require('react-native');
const { Dimensions, StyleSheet } = React;

module.exports = StyleSheet.create({
searchResultsContainer: {
width: Dimensions.get('window').width - 20,
alignItems: 'flex-start',
flex: 9
},
})

And you should see something like this on the emulator:

Basic Layout of both components

Setup Google Custom Search API Key

Is not in the scope of this tutorial to explain how to obtain a Google Custom Search API Key. You can read the great documentation provided by Google on the subject:

After you get your Google Custom Search API Key let’s set it up in our project.

First I always like to create a constants folder to store values that I use on the app but rarely change across the hole flow of the application. So let’s create that folder with an file inside and let’s call it api.js with the following content:

// Google Custom Search API KEY
export const API_KEY=’<GOOGLE Custom Search API KEY>’
It’s important to say that this file should not be uploaded to your repository so you don’t share your API Key, so add it to your .gitignore file.

Making our first request to Google

Ok let’s make our first request to get the Search Results from Google Custom Search API. On the first go let’s just try to get the results and print them on the console.

The Google Custom Search API uses REST requests and we need to provide some values to make it work:

  • API key — Use the key query parameter to identify your application.
  • Custom search engine ID — Use cx to specify the custom search engine you want to use to perform this search. The search engine must be created with the Control Panel
  • Search query — Use the q query parameter to specify your search expression.

For more details on how to generate the Custom search engine UID please visit this link:

Google’s API act as a service so you only need to make a request to an URI to get the desired results. This is the general structure of the URI:

https://www.googleapis.com/customsearch/v1?[parameters]

and an example:

GET https://www.googleapis.com/customsearch/v1?key=INSERT_YOUR_API_KEY&cx=017576662512468239146:omuauf_lfve&q=lectures

Let’s add a function that uses fetch on our SearchBar.js component:

searchOnMedium = (searchParam) => {
let URL = apiURL + '?key=' + API_KEY + '&cx=' + cx + '&q=' + searchParam;
console.log(URL);
fetch(URL, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
}
}).then((response) => {
console.log(response)
error.message }));
}).catch((error) => console.log(error));
}

then let’s just modified the button to add the onPress action to call this function:

...
<TouchableOpacity
style={styles.textSearchButton}
onPress={() => this.searchOnMedium('react native')}
>
<FontAwesome name="search" size={16} color="#000"/>
</TouchableOpacity>
...

This will generate something like this on the javascript console when we press the button:

Response OK from Google API

Now that we have the data let’s use Redux to pass store it and pass it to the SearchResults.js component.

Redux implementation

I know, I know, Redux is an overkill for this app but I’m using it with the intention of make a reference demo on how to use it.

If you have never used Redux I recommend that you read this great tutorial:

https://redux.js.org/

But let’s give a brief intro before starting to code.

Redux is a state manager library that allows us to have a consolidated state on our app that components can consume from. Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. These restrictions are reflected in the three principles of Redux:

  • Single source of truth: The state of your whole application is stored in an object tree within a single store.
  • State is read-only: The only way to change the state is to emit an action, an object describing what happened.
  • Changes are made with pure functions: To specify how the state tree is transformed by actions, you write pure reducers(functions).

Redux has three main components:

  • Actions: are payloads of information that send data from your application to your store. They are the only source of information for the store. Actions are plain JavaScript objects. Actions must have a type property that indicates the type of action being performed. Types should typically be defined as string constants.

An example of an action that adds a new TODO:

const ADD_TODO = 'ADD_TODO'

{
type: ADD_TODO,
text: 'Build my first Redux app'
}
  • Reducers: specify how the application’s state changes in response to actions sent to the store. Remember that actions only describe what happened, but don’t describe how the application’s state changes.

An example of a reducer to add new TODO when the proper action is triggered:

function todos(state = [], action) {
  switch (action.type) {
    case ADD_TODO:
      return [
        ...state,
        {
           text: action.text,
           completed: false
        }
      ]
     default:
       return state
  }
}
  • Store: is an object that ties the actions and the reducers and has the following responsabilities:

1.- Holds application state;

2.- Allows access to state via getState();

3.- Allows state to be updated .

4.- Registers listeners.

5.- Handles unregistering of listeners .

Based on what we just saw let’s implement Redux in our project.

Action

Let’s create and actions folder with a file called index.js with the following content:

export const SEARCH_RESULTS = 'SEARCH_RESULTS';

export function searchResults(results) {
return {
type: SEARCH_RESULTS,
results
}
}

Since we are going to store the results as and array we are going to return the results array once the SEARCH_RESULTS action is triggered.

Reducer

Now let’s create a reducers folder with and index.js file with the following content:

import {combineReducers} from 'redux';
import {
SEARCH_RESULTS,
} from "../actions";

function results(state = [], action) {
switch (action.type) {
case SEARCH_RESULTS:
return action.results;
default:
return state;
}
}

const rootReducer = combineReducers({
results,
});

export default rootReducer;

Although we don’t need here because we have only one reducer and one action, I included in case that you have more than one reducer and action. The combineReducers function does exactly that combine several functions into an object that you can include and call the necessary reducer as needed.

Store

Now let’s create an store for our global state. In our App.js file let’s add the following:

...
// Redux Store
import {createStore} from 'redux';
import {Provider} from 'react-redux';
import rootReducer from './reducers';

const store = createStore(rootReducer);
store.subscribe(() => console.log('store', store.getState()));
...
<Provider store={store}>
<View style={styles.container}>
<SearchBar/>
<SearchResults/>
</View>
</Provider>

Besides importing the necessary components and creating the store, we subscribe to every change on the store so we could see on the console if the changes are what we are expecting. Once is working as expected we can remove that line from our app.

Using Redux

Now let’s use Redux to store the results of our search.

So to connect the button click with the action to store the search results on the Store we need to make some minor changes on our SearchBar.js component. First lets import the connect function from react-redux library and then our action.

...
import {connect} from 'react-redux';
import {searchResults} from "../../actions";
...

After that we need to connect our component with the action and the store by exporting our component like this:

...
export default connect(null, {searchResults})(SearchBar);

Finally we could use the function to store our search results in our centralized state.

searchOnMedium = (searchParam) => {
let URL = apiURL + '?key=' + API_KEY + '&cx=' + cx + '&q=' + searchParam;
fetch(URL, {
method: 'GET',
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
}
}).then((response) => {
response.json().then((data) => {
this.props.searchResults(data.items);
})
}).catch((error) => console.log(error));
}

And the result, as we are subscribed to show every change to the store in the console, should be something like this:

Results of our search in Redux’s Store

Search what the user types on the input

We have being searching the same term over and over again it’s time to connect our input in the SearchBar.js component to make possible to the user to find for desired terms on Medium.

On your SearchBar.js component let’s make some minor changes. First let’s add our constructor and our component state to manage the input change while the user’s type into it.

...
class SearchBar extends Component {
constructor(props) {
super(props);
this.state = {
searchTerm: ''
}

}
...

Now that we have our state will change it on text changes in the input while the user types.

...
<TextInput
placeholder='Enter your search terms'
style={styles.textInputSearch}
underlineColorAndroid={'transparent'}
onChangeText={(searchTerm) => this.setState({ searchTerm })}
value={this.state.searchTerm}

/>
...

And finally in our searchOnMedium function we will use the state value of searchTerm to make the Search.

...
searchOnMedium = () => {
let URL = apiURL + '?key=' + API_KEY + '&cx=' + cx + '&q=' + this.state.searchTerm;
fetch(URL, {
...

Consuming Store values on another component

Finally it’s time to consume the Store values of our search in another component.

First we are going to show just some basic Text component with the name of the articles that results from the search and then we are going to add a link to the original article and some styling to make them look a little bit prettier.

To achieve this we need to modify our SearchResults component in the following way:

import React, {Component} from 'react';
import {Text, View} from 'react-native';

import {connect} from 'react-redux';

const styles = require('./SearchResultsStyles');

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

render() {
return(
<View style={styles.searchResultsContainer}>
{this.props.results.map((result, key) => (<Text key={key}>{result.title}</Text>))}
</View>
)
}
}

function mapStateToProps(state) {
return {
results: state.results
};
}

export default connect(mapStateToProps, null)(SearchResults);

as you can see we created a new function that will map the content of the Redux Store(the global state) into a prop called results. Then we need to connect Redux Store to the component with the connect function. And then we can consume the global state of Redux like any other prop and display a text with the name of the article in our component.

Now to visit the article on the web browser when linking the title we need to add a TouchableOpacity component with a a link on the onPress action using the Linking class from React Native, the code should like like this:

import React, {Component} from 'react';
import {Linking, Text, TouchableOpacity, View} from 'react-native';
import {connect} from 'react-redux';
const styles = require('./SearchResultsStyles');

class SearchResults extends Component {
constructor(props) {
super(props);
}
render() {
return(
<View style={styles.searchResultsContainer}>
{this.props.results.map((result, key) => (
<TouchableOpacity
onPress={() => {Linking.openURL(result.formattedUrl) }}
>
<Text key={key}>{result.title}</Text>
</TouchableOpacity>
))}
</View>
)
}}
function mapStateToProps(state) {
return {
results: state.results
};
}
export default connect(mapStateToProps, null)(SearchResults);

Finally we just need to add some styles to our TouchableOpacity to make it look a little bit prettier, so we modify our SearchResultsStyles.js:

const React = require('react-native');
const { Dimensions, StyleSheet } = React;
module.exports = StyleSheet.create({
searchResultsContainer: {
width: Dimensions.get('window').width - 20,
alignItems: 'center',
flex: 9
},
  resultLink: {
display: 'flex',
backgroundColor: '#ddd',
borderRadius: 5,
height: 40,
width: Dimensions.get('window').width - 10,
marginVertical: 5,
padding: 5,
alignItems: 'center',
justifyContent: 'center'
}
})

And that’s it your final code should look something like the following:

The final result:

You can checkout the code in this repository:

Or test the app with Expo using this link:

See you soon…

I hope you find this article useful. If you have any question or suggestion let me know or leave a comment.

Author: Eng. Alberto Aragón Alvarez

If you like it, clap. For more histories like this one follow our publication on Medium.

If you want to collaborate with us please visit our web site:

www.alturasoluciones.com