I’ve finished my final portfolio project in the Flatiron School’s full stack web development curriculum! It’s satisfying to be able to say that. Since the emphasis on this project was to use React/Redux, I decided to do something a little more interactive than my previous projects – a typing wpm tester. Rather than having the user simply enter information to be stored and displayed, the user now picks a team, takes a test, and is scored – and they can see the top scores and some general stats on test results.
I started out by creating a simple Rails app that can create, store, and spit out test results via JSON. Basically it handles the preservation of data when wpm tests are taken, and then sends that raw data back out upon request. Then I used React and Redux to handle the rest – actually administrating the wpm test, sending the results to the rails API, and then fetching information from that API to show stats involving previous tests.
Since both parts are really doing separate, succinct jobs, they are running on different ports and out of different folders, as opposed to my previous projects consisting of stand-alone apps. However, neither one is very useful without the other, so they have to be able to talk to each other. To set this up, I not only needed to have the react app making asynchronous requests properly, but I also needed to make sure the Rails API accepted the requests as valid – because the requests come from a different origin than the server/port the rails API runs on. I learned about a gem called ‘rack-cors’ that can be used in Rails, which adds an initialization file called cors.rb that could ensure that the API accepts cross-origin AJAX requests. Here’s what that looks like:
1
2
3
4
5
6
7
8
9
|
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins 'localhost:3000'
resource '*',
headers: :any,
methods: [:get, :post, :put, :patch, :delete, :options, :head]
end
end
|
For context, I run the Rails server on localhost:3001 instead of the default 3000, to leave that open for the React app to use. This particular configuration gives a lot of freedom for the front end to basically make whatever request it wants, and it will be accepted, but it is pretty easy to see that that could be limited in different ways if I wanted to add restrictions, like removing a method, or specifying a resource instead of using a wildcard.
When I had created the framework for my wpm test, one thing that was bugging me was that I didn’t have anything to tell me when I had made a mistake. I had decided to only allow submission when the text sample had been copied correctly, and in full – and that’s pretty easy to check for when someone clicks the submit button. It’s a little trickier to tell them ahead of time that they’ve made a mistake, so they can fix it, know it’s been fixed, and move on. In React, when someone types into a text-field, there’s more going on behind the scenes than a regular html page without JavaScript. Each time change is made (a key is pressed) it updates a value which is then immediately rendered back into the text-field so you can see what you typed. When Redux is being used, this value is stored within the app in a place it can’t even be directly edited. You have to dispatch an action to update the Redux state, and then that gets sent back down to your React component as props, so that you can use that value. It’s a bit convoluted, but it essentially allows you to have better control over state, and things aren’t changing around without you being pretty explicit about what you want changed.
Anyway, whenever you have text input, you’re going to have a handleOnChange function to make sure that stuff happens. I saw this as the perfect place to add some extra logic to check for correctness, any time the text was changed in the text-field. All told, it looks like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
handleOnChange = event => {
const { name, value } = event.target;
const currentTestFormData = Object.assign({}, this.props.testFormData, {
[name]: value
})
this.props.updateTestFormData(currentTestFormData)
let samplePart = this.state.sampleText.substring(0, currentTestFormData.words.length);
if (((currentTestFormData.words !== samplePart) || samplePart.length > currentTestFormData.words.length) && this.state.correct) {
this.setState({
correct: false
})
} else if (!this.state.correct && currentTestFormData.words === samplePart) {
this.setState({
correct: true
})
}
}
|
Basically, it sees how much as been typed, and checks against the text sample, only up to that point. That way it’ll only correct you when you get something wrong – rather than telling you it’s wrong simply because you haven’t finished typing (Some web forms do this by default, and it’s so frustrating as a user!!!). You may have noticed that it’s just storing this correctness boolean directly in the component’s state. I have no need to keep this in the React state – it’s just an isolated quick check, which ultimately dictates whether CSS will be used to turn the text red or not. Nothing else will use that information, and I didn’t want to add unnecessary functions to store it in an application-wide state and have to retrieve it again.
I learned that it’s generally a good idea to use Redux to store information in state, but that it’s also ok to have state stored in components sometimes. It can depend on convenience, and how you want things to be organized.
This project was a lot of fun. I started out a bit worried about its complexity, but it turned out simpler than I expected. I could of course, at some point add complexity to it, like making user profiles that store their scores, rather than just anonymous submissions for teams. I look forward to expanding my knowledge and experience using React and Redux – my next project will probably involve using an 3rd party API – that way I can have access to more interesting existing data, and focus on creating an even more polished front-end with more features.