React Hooks: Wikipedia search bar with useEffect hook
Intro
We are going to make a search bar that queries the Wikipedia API and shows the results on the screen.
Scaffolding the Search Bar
We start of course with our main App component.
import React from "react"
import Search from "./components/Search"
export default () => {
return (
<div>
<Search />
</div>
)
}
and our Search component
import React, { useState } from "react"
const Search = () => {
const [term, setTerm] = useState("")
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
The search component is very simple. We make an input and use the useState hook to track the value of the input.
The useEffect Hook
The useEffect hook allows functional components to use something like lifecycle methods. Note however that you will never use lifecycle methods like componentDidMount
in a functional component.
We configure the useEffect hook to run some code automatically in one of three scenarios.
- When the component is rendered for the first time only
- When the component is rendered for the first time and whenever it re-renders
- When the component is rendered for the first time and whenever it re-renders and some piece of data has changed
The useEffect hook takes two arguments.
- A function
- Direction on when that function is executed (one of the 3 scenarios above)
The form of the direction is always going to be
- An empty array
- An array with one or more elements inside of it
- No array at all
So all in all that would look roughly like this
useEffect({function}, {array})
useEffect Sample
Here we can see an example of the useEffect hook in action. Because our little input here has an onChange
listener, the component is going to re-render with every keypress into the input. Which means that our 2nd console log is going to fire with every keypress. By contrast our console log inside of the useEffect hook is only going to fire on the initial render.
import React, { useState, useEffect } from "react"
const Search = () => {
const [term, setTerm] = useState("")
// first argument of useEffect is always a function
// second argument controls when code is executed
useEffect(() => {
console.log("RUN ON INITIAL RENDER") // highlight-line
}, [])
// every keypress re-renders component
console.log("RUN ON EVERY RENDER") // highlight-line
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
We could then modify the second argument of useEffect to remove the array, and the console log will then run for every re-render.
import React, { useState, useEffect } from "react"
const Search = () => {
const [term, setTerm] = useState("")
// first argument of useEffect is always a function
// second argument controls when code is executed
useEffect(() => {
console.log("RUN AT INITIAL RENDER AND EVERY RE-RENDER")
}) // highlight-line
// every keypress re-renders component
console.log("RUN ON EVERY RENDER")
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
If we did a third example where we included an array with an argument in it (like term
) we would see the exact same thing in this case, because the term is changing with every keypress.
Now that we understand how useEffect works, we can go ahead and make our API request inside of our function.
API Request to Wikipedia
As usual, when making API request in React we are going to use the Axios package, so get that installed. Then we need to figure out how we are going to configure the useEffect hook.
// first argument of useEffect is always a function
// second argument controls when code is executed
useEffect(() => {
// search wikipedia api
}, [term])
This is a good start as we have specified WHEN we want the API request to happen (on all component renders and term changes). The API request itself is a bit tricky.
No Async Await in useEffect Hook
You would think that we could do the following
useEffect(async () => {
await axios("api-url")
}, [term])
But we are not allowed to use Async directly as the first argument. This is just something you need to know, but you will get a console warning if you break it.
Ways To Get Around No Async
The best way is to just use a helper function inside of this function and use Async on that.
useEffect(() => {
const search = async () => {
await axios.get("api-url")
}
search()
}, [term])
This is the recommended method to get around this restriction.
And just for fun here is an alternate syntax on this.
useEffect(() => {
;(async () => {
await axios.get("api-url")
})()
}, [term])
Wait what is this syntax? Double parentheses ()()
? This is a way to define an anonymous function and immediately call it, and it is valid syntax. Learn something new everyday.
Final Request Sample
Here is what we end up with for our Wikipedia request.
import React, { useState, useEffect } from "react"
import axios from "axios"
const Search = () => {
const [term, setTerm] = useState("")
useEffect(() => {
const search = async () => {
await axios.get("https://en.wikipedia.org/w/api.php", {
params: {
action: "query",
list: "search",
origin: "*",
format: "json",
srsearch: term,
},
})
}
search()
}, [term])
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
And if we check out network tab we can see that our API requests are successful.
Manipulating Search Results
Now that we are receiving data from the Wikipedia API we need to be able to manipulate it. We need to create a new state in the component and have that state represent the search results.
import React, { useState, useEffect } from "react"
import axios from "axios"
const Search = () => {
const [term, setTerm] = useState("")
const [results, setResults] = useState([]) // highlight-line
useEffect(() => {
const search = async () => {
const { data } = await axios.get("https://en.wikipedia.org/w/api.php", {
// highlight-line
params: {
action: "query",
list: "search",
origin: "*",
format: "json",
srsearch: term,
},
})
setResults(data) // highlight-line
}
if (term) {
search()
}
}, [term])
console.log(results) // highlight-line
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
And this does get us the request results in the console, however we have a small problem.
Our initial request is a blank string, so we are requesting nothing, and Wikipedia is whining about that. There are a couple of things that we could do to fix this. The first is to just implement a quick check to make sure that term
exists before we run search()
import React, { useState, useEffect } from "react"
import axios from "axios"
const Search = () => {
const [term, setTerm] = useState("")
const [results, setResults] = useState([])
useEffect(() => {
const search = async () => {
const { data } = await axios.get("https://en.wikipedia.org/w/api.php", {
params: {
action: "query",
list: "search",
origin: "*",
format: "json",
srsearch: term,
},
})
setResults(data)
}
// quick check for term value so we don't request search of empty string
if (term) {
search()
}
}, [term])
console.log(results)
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
or another solution would be provide a default search term, like React...
import React, { useState, useEffect } from "react"
import axios from "axios"
const Search = () => {
const [term, setTerm] = useState("React") // highlight-line
const [results, setResults] = useState([])
useEffect(() => {
const search = async () => {
const { data } = await axios.get("https://en.wikipedia.org/w/api.php", {
params: {
action: "query",
list: "search",
origin: "*",
format: "json",
srsearch: term,
},
})
setResults(data)
}
search()
}, [term])
console.log(results)
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
</div>
)
}
export default Search
We will take the 2nd route so that our users have something to look at when the widget loads.
Mapping and Displaying Search Results
Now that we have the search results, we just need to map over them and display them.
import React, { useState, useEffect } from "react"
import axios from "axios"
const Search = () => {
const [term, setTerm] = useState("React")
const [results, setResults] = useState([])
useEffect(() => {
const search = async () => {
const { data } = await axios.get("https://en.wikipedia.org/w/api.php", {
params: {
action: "query",
list: "search",
origin: "*",
format: "json",
srsearch: term,
},
})
setResults(data.query.search)
}
search()
}, [term])
const searchResultsMapped = results.map(result => {
return (
<div className="item" key={result.pageid}>
<div className="content">
<div className="header">{result.title}</div>
</div>
{result.snippet}
</div>
)
})
return (
<div>
<div className="ui form">
<div className="field">
<label>Search Term</label>
<input
className="input"
value={term}
onChange={e => setTerm(e.target.value)}
/>
</div>
</div>
<div className="ui celled list">{searchResultsMapped}</div> // highlight-line
</div>
)
}
export default Search
Which is great except that the content is returned in HTML format, so we need to change how we handle this data. If we don't we could be vulnerable to an XSS attack Wikipedia:Cross-site Scripting.
We will cover that in our next post.
GitHub Repo
Comments
Recent Work
Basalt
basalt.softwareFree desktop AI Chat client, designed for developers and businesses. Unlocks advanced model settings only available in the API. Includes quality of life features like custom syntax highlighting.
BidBear
bidbear.ioBidbear is a report automation tool. It downloads Amazon Seller and Advertising reports, daily, to a private database. It then merges and formats the data into beautiful, on demand, exportable performance reports.