Project Here
100 Days of Code Day 8 - 10
This is a personal reflection post detailing the process I went through to create my two and a half day project, which is a Pokemon Berry stats page using PokéAPI! A quick note that I am a junior coder, so if you think I've done something strange please tell me. This reflection post is so that I can expand upon my thought process and try and solidify what I've learned.
Objectives
- Use an API to gather and display data visually
- Use data from one API to look up another entry and get data
- Learn TypeScript/use it for React
Planned Features
- Flavor Levels
- Relative sizing scales
- Growth Cycle Display with timing?
Bugs & Regrets
- Meter for growth hours out of day not showing up on Chrome
-
setInterval timing weirdness??got it to work with what feels like a hack - Not taking in process screenshots
- No uses for CSS transitions/no interactivity
TypeScript
This is the first time I didn't directly follow a tutorial for my introduction into a concept or language. I already know the basics of C#, so I just found examples of TypeScript used in a React setting and worked by searching what errors I got. So here's how in it works from my understanding.
In TypeScript you must type all of your variables. Easy enough. You need to know what types are going in. Well, it's not always the easiest. It became cumbersome when dealing with a lot of data being passed through to the state for my berry look ups and having to define what was in every part of it. Maybe I'm doing it wrong, but from what I saw I have to essentially define things twice. Once in the interface, and then once more giving initial values in the constructor. I'll be honest, I don't know the deeper meaning of the interface, it's just necessary to define your state and props in an orderly manner.
Growth Cycle Component:
import React, { Component } from 'react';
interface IProps {
id: number,
growth_time: number
}
interface IState {
timer: any,
stage: string,
x: number
}
class Growth extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
stage: '/growth/AllTreeSeed.png',
timer: setInterval(this.setTimer, 0),
x: 1
}
}
render() {
const { stage } = this.state;
return (
<img src={stage} />
)
}
setTimer = () => {
if (this.props.growth_time != 0) {
clearInterval(this.state.timer);
this.setState({ timer: setInterval(this.changeDisplay, 1000 * this.props.growth_time) });
}
}
changeDisplay = () => {
if (this.state.x >= 5) {
this.setState({ x: 0 });
}
switch (this.state.x) {
case 0: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/AllTreeSeed.png` }); break;
case 1: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/AllTreeSprout.png` }); break;
case 2: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/${this.props.id}Taller.png` }); break;
case 3: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/${this.props.id}Bloom.png` }); break;
case 4: this.setState({ stage: `${process.env.PUBLIC_URL}/growth/${this.props.id}Berry.png` }); break;
}
this.setState({ x: this.state.x + 1 })
}
componentWillUnmount() {
clearInterval(this.state.timer);
}
}
export default Growth;
setInterval & Hacks
The goal of this is that the growth cycle graphics update slower based on how long it takes to grow. Each 'hour' is 1 second.
Just now fixed the issue I was having before, couldn't let it sit while I was writing this article. Originally I set the timer in the state to 5000 * this.props.growth_time, but the problem is that in my containing component, when initializing my values growth time is set to 0. So when the component begins, the interval is set to update every 0 miliseconds, and will update like crazy until we clear that timer and start a new one. This problem has persisted before, where I need to update a function once the correct props are received, however componentDidUpdate() will now allow me to set a state as it will cause a recursion and is defunt. Likewise, while it doesn't completely error, calling setTimer in the render function gives you a warning about trying to update things every time it renders.
The solution, which feels like a hack, is to keep calling setInterval (as the original timer is set to 0) until we've received the growth_time props at not 0, and once it does have it's props (or rather the props are what I want it to be) clear the interval, as I learned it will keep going even if I change the timer state, and set a new interval for changeDisplay to go.
Math
Ew, math. I would say I'm too dumb to get math, but that's what I said about coding and now look where I am! However I do still struggle with the best solution for a lot of things even simple, so let's take a step through it.
From the API we are given a potency in a number/40. This number will either be 10 or 5's place for each flavor of the berry, so per our representation we can end up with half stars. We will have a star be worth 10 points, so we take out potency and divide it by 10 to just get the 1's place and how many filled in AND half-filled in stars we will have, despite being named fullStars. The stars are executed with a for loop, so if we are .5 over we still will create another star.
To determine if we have a half star or not, we use the modulo operator to see if when divided with 10, do we have a remainder? Using a ternary operator (fancy small if statement) if the remainder is 0 (false), we say we have no half stars. If the remainder is any number that is not 0 (true), we know we have one half star.
We calculate the empty stars with what's left over, then use another ternary operator to see if there are halfStars. If there are, we want to replace one of the full stars with it.
let potency = this.props.potency;
let fullStars: number = potency / 10;
let halfStars: number = potency % 10 ? 0 : 1;
let emptyStars: number = 4 - fullStars;
halfStars ? null : fullStars--;
PokéAPI
I've used PokéAPI for gathering most of the berry data. I hand saved (200+) images for the growth cycle and the size comparisons, but I actually got the berry icon by chaining get requests with axios. The berry information is separate from the item listing for the berry, which contains a url to the item image for it.
fetchBerry() {
axios.get(`https://pokeapi.co/api/v2/berry/${this.props.berryID}`)
.then(res => {
this.setState({ info: res.data });
return axios.get(`${this.state.info.item.url}`)
}).then(res => {
this.setState({ item: res.data });
})
}
Design
I've seen a bit of debate on if you should have your CSS inline with your code, or have it outside it's own file. I think I prefer outside in it's own files, but I find that it's handy to declare dynamic styles inline. For the size of each berry, we are given a number, and I've directly translated that into the height for the images, then divided by 3 so some of the largest ones don't break the design. You still end up with extremely small berries, but as long as the relational sizes are the same, I think this is okay as long as we have the icon sprite to show it's intended design.
<img
src={`${process.env.PUBLIC_URL}/size/${this.props.id}.png`}
style={{ height: `${this.props.size / 3}px` }}
/>
What's next?
I need to bolster my CSS Animation skills, so I need something interactive, but I'm not sure what yet. I've adopted the use of Notation from a recommendation I found in the comments here, so I've been keeping track of what I want to learn and what project ideas I, or my friends have. I read about live share for VSCode, and tried it out with my very good friend/mentor and I'm really excited about it!
Localhost isn't local anymore
Jonathan Carter ・ Mar 26 '19
We've been pair programming for awhile without it, but I really want to do some kind of project with her using this, I just have to come up with a good idea!
Top comments (6)
Hi Nina,
With TypeScript, there's a lot of benefits in using the interface for type checking.
You could create the interface for type checking, then export it from an interfaces file or it's own file. Then in the components you need it, you can import the interface as a type.
Also, interfaces are used for type checking at compile time, but then thrown away at build time so it doesn't bloat your app size which make great replacements for object classes often only instantiated to type check the shape of a data object.
Thanks for the tips!
Love your organization using Notion! As a fellow pokemon fan I'm getting inspired to do build something related to it as well!
Great! Can't wait to see what you make!
Lovely! I was thinking about coding an online Pokédex to test myself, what do you think?.
Excelent work!
I think that would be great! Even if it's been done before, I think pokemon info is a great way to parse and represent a lot of data as a way to test yourself.