I'm making a random color of the day web application using a random color API I found online. Everything is working so far, but since I'm new to JavaScript and React, I'm a bit curious on how I would limit an API request to once per day. The way the API works now is that every time you refresh the page, a new color will appear every time. Is there any way to limit this to one color that will appear per day - the same color - no matter how many times you refresh the page? Thanks!
import React, { Component } from 'react';
import './App.css';
class App extends Component {
constructor() {
super()
this.state = {
items: [],
isLoaded: true
}
}
componentDidMount() {
fetch("http://www.colr.org/json/colors/random/7")
.then(res => res.json())
.then(res => {
this.setState({
isLoaded: true,
items: res.colors
});
})
}
render() {
var itemName = this.state.items.map(item => item.id)
var itemHex = this.state.items.map(item => item.hex)
//var itemHex = items.map(item => <div key={item.id}>{item.hex}</div>)
if (!(this.state.isLoaded)) {
return (
<div>
<h1>Not Loaded!</h1>
</div>
)
}
else {
return (
<section style={{ backgroundColor: "#" + itemHex[0]}} className="App">
<h1>JR's color of the day is: <h2 style={{color: "#" + itemHex[4]}}>{itemName[0]}.</h2></h1>
<h1>also, the hex is: {"#" + itemHex[0]}</h1>
<h4>here are some other colors that go w/ it</h4>
<div style={{backgroundColor: "#" + itemHex[1]}} className="rectangle1"></div>
<div style={{backgroundColor: "#" + itemHex[2]}} className="rectangle2"></div>
<div style={{backgroundColor: "#" + itemHex[3]}} className="rectangle3"></div>
<h3>data courtesy of the color API, colr.org</h3>
</section>
);
}
}
}
export default App;
you just need to store the date and colors on each fetch. and invalidate your cache based on today's date string and stored one.
componentDidMount() {
let cachedColors;
if(localStorage.getItem('cached-colors'))
cachedColors = JSON.parse(localStorage.getItem('cached-colors'));
// setting cachedColors to null if it wasn't stored today
if(cachedColors && new Date().toDateString() !== cachedColors.date)
cachedColors = null;
// if cachedColors still got value, it means we can use it as valid cache for today
if(cachedColors)
this.setState({
isLoaded: true,
items: cachedColors.value
});
else
fetch("http://www.colr.org/json/colors/random/7")
.then(res => res.json())
.then(res => {
this.setState({
isLoaded: true,
items: res.colors
});
})
}
How to limit an API request to once per day in React?
You can't, really. Rate-limiting an API is done on the server. Anybody can clear their cookies, or local storage, or whatever other means of persistence you use in the browser to rate-limit requests.
I realize this is a learning exercise, but there is no point in learning a technique that has no real-world use.
https://www.npmjs.com/package/memory-cache is the solution. Here are the examples of the API Usage.
This does what you're looking for by storing the current color in the browser for one day. The code should be clear, but just ask if it's not.
(Tested in Chrome with a Node backend, but should be fine in React)
let now = new Date().getTime(); // number of milliseconds since the 60's ended in London
const oneDay = 1000 * 60 * 60 * 24; // number of milliseconds in a day
if(localStorage.color && localStorage.expireTime && parseInt(localStorage.expireTime) > now){
let storedColor = localStorage.color; // or localStorage.getItem("color")
console.log(`Returning user -- Color from last visit is ${storedColor}`);
}
else{
let newColor = "green"; // Set your new color here
let newExpireTime = now + oneDay;
localStorage.setItem("color", newColor);
localStorage.setItem("expireTime", newExpireTime);
let dateString = new Date(newExpireTime).toLocaleString();
console.log(`First visit (since storage was cleared). New color, ${newColor}, will be replaced at ${dateString}`);
}
(Edit: Removed html output and added the info to console.log instead,
removed 'localStorage.clear()', which was for debugging)
Related
I'm working on a site that takes fairly long to build/deploy. I sometimes need information that is only available server-side for debugging. Using console.log is annoying since adding a console.log in the code and building takes too long.
But I also don't want the site to always log that information to the console.
My plan was to have a wrapper function for console.log, that checks if there is e.g dev_config set in localStorage.
For example to activate certain verbose levels that then show respective logs, or log only in certain sections.
Would this have a significant impact on performance of the site?
For instance something like this:
const devLog = (message) => {
devConfig = JSON.parse(localStorage.getItem('dev_config'))
if (devConfig != null) {
// ...
// other checks (set devMode to true)
// ...
if (devMode === true) {
const now = new Date()
const hours = now.getHours()
const min = (now.getMinutes() + '').padStart(2, '0')
const sec = (now.getSeconds() + '').padStart(2, '0')
console.log(`[${hours}:${min}:${sec}] `, message)
}
}
}
PS: I am aware of the built-in browser dev tools and I am using them in most cases. But since the information in my current problem is server side, I don't think I can get with the dev tools what I need.
You could overwrite console.log but that could annoy you later on. I prefer to have my own logger like your devLog function. It's more explicit.
As #Barmar suggested in the comments you could instead check the localStorage on load instead of on every call. I have something similar to the below in a few projects of mine:
{
let devConfig = JSON.parse(localStorage.getItem('dev_config'));
devLog = (...args) => {
if (devConfig) {
const time = new Date().toLocaleTimeString()
console.log(`[${time}] `, ...args)
}
};
devLog.activate = () => {
devConfig = true;
localStorage.setItem('dev_config', true)
}
devLog.deactivate = () => {
devConfig = false;
localStorage.setItem('dev_config', false)
}
devLog.toggle = () => {
if ( devConfig ) {
devLog.deactivate()
} else {
devLog.activate()
}
}
}
Since when you're reading dev_config from the localStorage for the first time you'll get null it will start off deactivated - which seems like a good default to me.
Currently working with NextJS, but struggling to make an indexing page of sorts. With the router, I'm trying to get the page number by doing this:
let router = useRouter()
let page = isNaN(router.query.page) ? 1 : parseInt(router.query.page);
So that when I go to page=1, page=2 etc, I get new sets of data.
The functionality for this is called in the same main component, and is a React Query function:
const {data, status} = useQuery(["keycaps", {manu: manuId}, {prof: profileId}, {col: colorId}, {stat: statusId}], getKeycaps)
And said function looks like this:
const getKeycaps = async(key) => {
const manuId = key.queryKey[1].manu
const profileIds = key.queryKey[2].prof.map(id => `profile.id=${id}`)
const colorIds = key.queryKey[3].col.map(id => `filter_colors.id=${id}`)
const statId = key.queryKey[4].stat
const profileQueryString = profileIds.join("&")
const colorQueryString = colorIds.join("&")
let urlParams = new URLSearchParams(document.location.search);
let page = urlParams.get("page") == null ? 1 : parseInt(urlParams.get("page"));
let start = (page * 10) - 10
const data = await axios(`
${process.env.REACT_APP_STRAPI_API}/keycaps?${manuId ? 'manufacturer.id=' + manuId + '&' : ''}${profileQueryString ? profileQueryString + '&' : ''}${colorQueryString ? colorQueryString + '&' : ''}_start=${start}&_limit=10`)
return data.data
}
When initially loading pages, like directly pasting the url of the index in (i.e. keycaps?page=2), it will get all the results all fine. However, if I start using navigational buttons like this:
<Link href={`/keycaps?page=${page - 1}`}> // next/link
<div className="w-32 rounded-lg cursor-pointer">
Prev
</div>
</Link>
<Link href={`/keycaps?page=${page + 1}`}>
<div className="w-32 rounded-lg cursor-pointer">
Next
</div>
</Link>
The whole thing starts to break down. Essentially, the page doesn't actually reload any data or results until the page is unfocused. So, if I press the Next button to go to the next page, it won't load the data until I do something like tab to a new window or check a different internet tab, and then when I come back, the data will all magically load within a second.
I've tried this with next build && next start too, and this produces the same results. I just want to get the page results when the next and prev page buttons are pressed, and in a way that doesn't require the user to switch tabs to get content.
I will note that I do have a getStaticProps on this as well. It does the following:
export async function getStaticProps() {
const allKeycaps = (await getAllKeycaps() || 'Error')
return {
props: { allKeycaps }
}
}
Which will call an api script, and said script does this:
async function fetchAPI(query, {variables} = {}) {
const res = await fetch(`${process.env.REACT_APP_STRAPI_API}/graphql`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
console.error(json.errors)
throw new Error('Failed to fetch API')
}
console.log('json', json.data, variables)
return json.data
}
/* Keycap related grabs */
export async function getAllKeycaps() {
const data = await fetchAPI(
`
{
keycaps {
id
slug
name
published_at
updatedAt
profile {
id
name
}
manufacturer {
id
name
lead
}
status {
id
name
}
colors
filter_colors {
color
}
kits
designer
thumb {
formats
}
}
}
`
)
return data
}
Anyone know how to get this to work? To navigate between indexes like this? I've been trying to look for Next tutorials that use navigations like page 1, page 2 etc but all I can find is examples of blog articles with slugs, no index searches of any kind.
Thanks a lot in advance.
Answer found:
When setting data and status using useQuery
const curPage = router.query.page == null ? 1 : router.query.page
const {data, status} = useQuery(["keycaps", {manu: manuId}, {prof: profileId}, {col: colorId}, {stat: statusId}, {page: curPage}], getKeycaps)
And then, in getKeycaps
const page = key.queryKey[5].page
I guess the "urlParams" approach wasn't a good one? Or at least, not one that was updating quick enough. So passing through the router page number seems to work better.
I am creating a Mission Clock web app using React and Flux.
The code can be found here: https://github.com/jcadam14/Flux-Mission-Clock
Right now it's extremely basic, I'm new to React and Flux and it has been an extremely long time since I did any JavaScript (been in the monolithic Java application business for too long). This is to get a proof of concept so I can base my design around React/Flux.
The basic concept is a "Next Contact" timer counts down and when it hits 1min before completion, the box the counter is in turns red. Then when the NextContact timer completes, a CurrentContact timer starts, and a new NextContact timer should start.
Everything works fine up until the point where the NextContact component completes and is supposed to update with a new NextContact. The text in the component and the style update, but the Countdown does not start ticking down. It stays at the new value but doesn't start the timer.
Each time a render occurs because of some other reason, the NextContact component updates again with a new time but does not start counting.
However, if I save any change within any of the files (I'm using Visual Studio Code with module.hot active) then the counter starts and in fact picks up where it should be. So it seems like something isn't getting fully rendered on change like I would expect.
I've tried using forceUpdate but that didn't do anything, and I've tried various ways of getting the Counter component but nothing works.
Any help would be greatly appreciated. I'm hoping once I get this down and can understand how all the dispatching stuff works the rest of the application should fall into place (the timers are a central component to the app, everything else is rather simple).
EDIT: I also tried writing just a simple timer app with Countdown but this time using Redux, and the same thing happens. So I guess the question might be how do you force a component to re-initialize itself?
Thanks!
Jason
Well I ended up just writing my own counter, here it is:
import React,{Component} from 'react';
const formatValues = ({days,hours,minutes,seconds}) =>
{
const hourString = ('0' + hours).slice(-2);
const minString = ('0'+ minutes).slice(-2);
const secString = ('0' + seconds).slice(-2);
return (days + ':' + hourString + ':' + minString + ':' + secString);
}
class MCCountdown extends Component
{
constructor(props)
{
super(props);
this.state = {
endDate:this.props.endDate,
countdown:'0:00:00:00',
secondRemaining:0,
id:0
}
this.initializeCountdown = this.initializeCountdown.bind(this);
this.tick = this.tick.bind(this);
}
componentDidUpdate(prevProps, prevState)
{
if(this.props.endDate !== prevProps.endDate)
{
clearInterval(prevState.id);
this.setState({endDate:this.props.endDate});
this.initializeCountdown();
}
}
componentDidMount()
{
this.initializeCountdown();
}
tick() {
const values = this.getTimeRemaining(this.state.endDate);
this.setState({countdown:formatValues(values),secondRemaining:values.secondsLeft});
if(values.secondsLeft <= 0)
{
clearInterval(this.state.id);
if(this.props.onComplete)
{
this.props.onComplete();
}
return;
}
else
{
if(this.props.onTick)
{
this.props.onTick(this.state.secondRemaining);
}
}
}
getTimeRemaining(endtime){
const total = Date.parse(endtime) - Date.parse(new Date());
const seconds = Math.floor( (total/1000) % 60 );
const minutes = Math.floor( (total/1000/60) % 60 );
const hours = Math.floor( (total/(1000*60*60)) % 24 );
const days = Math.floor( total/(1000*60*60*24) );
return {
secondsLeft: total,
days,
hours,
minutes,
seconds
};
}
initializeCountdown(){
const values = this.getTimeRemaining(this.state.endDate);
const id = setInterval(() => this.tick(),1000);
this.setState({id:id,countdown:formatValues(values),secondRemaining:values.secondsLeft});
}
render()
{
const {countdown} = this.state;
return(<div>{countdown}</div>);
}
}
export default MCCountdown
This did the trick. Seems like perhaps the timers/counters I have all tried might be missing that componentDidUpdate() method because once I added that to my MCCountdown, the clock restarts when a new date is set on the component.
Not sure if this is the pretties it can be but it works and I'm pretty darn happy with that right now.
I have an issue which I'm beginning to suspect has no solution unless I drop React and return to jQuery. I want to create an app that is similar to https://tenno.tools/ or https://deathsnacks.com/wf/ These are sites which grab JSON data and update periodically.
I want to make a react app that uses axios to refresh the data once per minute with setTimeout, since the data changes often.
axiosFunc = () => {
axios.get('https://api.warframestat.us/pc').then(results => {
this.setState({
alerts: results.data.alerts
});
setTimeout(this.axiosFunc,1000 * 60);
})
}
componentDidMount() {
this.axiosFunc();
}
Next I need to use map to cycle through the alert array's objects and make individual components based off the objects' data that are active.
render() {
return (
<main className="content">
<header>{this.state.whichEvent.toUpperCase()}</header>
{this.state.alerts.map(alert => {
//Variables that pull time based data from the objects go here, and go into the component as props
<AlertsBox key={alert.id}/>
})}
</main>
);
}
Then I use the props and state within the component to make a timer, since the data from the JSON file have expiration dates...
let timer = () => {
//Extract the data from the original string
//Convert the UTC to locale time
let seconds = Math.round((this.state.eta/1000) % 60);
let minutes = Math.floor( (this.state.eta/1000/60) % 60 );
let hours = Math.floor( (this.state.eta/(1000*60*60)) % 24 );
let days = Math.floor( this.state.eta/(1000*60*60*24) );
return `${days >= 1? days + " days" : ""} ${hours >= 1? hours + " hrs" : ""} ${minutes} min ${seconds} sec`
}
And all of this works. I'm able to see the dynamic data from the JSON as they come in and leave, as well as the corresponding time. Now I just need to use setInterval in order to get the timer to tick every second. Is this possible? I asked a similar question here
How can I return values once per second using react and axios?
But again, I'm beginning to suspect that this isn't actually possible. Is it?
You'll want to use setInterval on the axiosFunc, no need to set that up inside the network request. Here's an example that calls your API every 5 seconds and renders a formatted date.
class Example extends React.Component {
constructor() {
super();
this.state = { alerts: [] };
}
axiosFunc = () => {
axios.get('https://api.warframestat.us/pc').then(results => {
this.setState({
alerts: results.data.alerts,
});
console.log('Updated the data!', results);
});
};
timer = time => {
// Your timer code goes here, just printing out example data here.
const date = new Date(time);
return `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`;
};
componentDidMount() {
this.axiosFunc();
this.interval = setInterval(this.axiosFunc, 5000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
if (!this.state.alerts.length) {
return <div />;
}
// Sorting the alerts every time we render.
const latest = this.state.alerts.sort((a, b) => {
return new Date(b.activation) - new Date(a.activation);
})[0];
return <div>{this.timer(latest.activation)}</div>;
}
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.17.1/axios.min.js"></script>
<div id="root"></div>
It's definitely possible. As you said, all of this works - which part is actually giving you trouble? Are you getting an error anywhere?
Personally, I'd think about using Redux in addition to React in an app like this because I like to separate the fetching of data from the presentation of data, but that's all just personal preference. I have an example app that uses setInterval directly in a React component, in case the move from setTimeout to setInterval is causing you pain.
This is hard to phrase into words but I want to get the returned value of a function that is stored in an object, add that value to an array and output it. I have made this all work but it doesn't change once ran again, so say it outputs the random number of 678 it will also output that same number next time and so on. I created this cool terminal in react that gets the command of the key if it exists and outputs it in my console by returning jsx.
Here is my file structure...
Here is my react code... ( I had issues formatting, here is a better version of the code below)
import React, { Component } from "react";
import {
BrowserRouter as Router,
Route,
Link
} from 'react-router-dom';
import "./Main.css";
import Commands from "../Commands/Commands.js"
class Main extends Component {
constructor(props){
super(props);
this.state = {inputValue: ''}
this.outputs = ["type 'help' for information about plunketTheTerminal"];
this.commands = Commands;
this.commands.keys = Object.keys(this.commands)
}
handleSubmit = (event) => {
if(event.key == "Enter") {
if( this.state.inputValue != "" ) {
if( this.commands.keys.includes(this.state.inputValue)) {
this.outputs.unshift(this.commands[this.state.inputValue]);
}
else{
this.outputs.unshift(`No command '${this.state.inputValue}' found.`)
}
}
document.querySelector(".input-section").value = "";
}
this.forceUpdate()
}
handleChange = (e) => {
this.setState({ inputValue: e.target.value })
}
render() {
return(
<div>
<div className="terminal-header"></div>
<div className="terminal-body">
<div className="terminal-contents-wrapper">
<div className="output-item-wrapper">
{
this.outputs.map((output, index) => {
return <h1 className="output-item" key={ index }>{output}</h1>;
})
}
</div>
<div className="input-section-wrapper">
<input type="text" className="input-section" onKeyPress={this.handleSubmit} onChange={this.handleChange}/>
<span className="input-section-label">plunketTheTerminal#plunketTheTerminal-H170-D3H:~$</span>
</div>
</div>
</div>
</div>
)
}
};
export default Main;
Finally my JavaScript file for the commands... ( again better code here )
const rand = () => {
return Math.floor(Math.random() * 1000 ) + 1;
}
const Commands = {
help: "plunketTheTerminal is a online terminal for fun, it contains lots of different functions and commands. In fact you can actually SUBMIT YOUR OWN COMMANDS at our github if you are good with computers :).",
rand: rand(),
"rand --info": "Will generate a random number between 1 and 1000",
}
export default Commands
EDIT: Trying to use either of the following returns nothing.
rand: () => (
"x"
),
or
rand: () => {
return "x"
},
Here is a demonstration of what is happening...
I type the command...
I get my output which is fine...
but from then on repeating that same command won't generate a new random number i will continue to repeat the first returned value.
I would really appreciate some help but please keep in mind I am still learning react and JavaScript and would love to hear any constructive feedback. I will also continue to update information if needed. Thanks!
Found out what was wrong, I was calling the function without its parentheses. I had to change the command file to the following...
const Commands = {
help: "plunketTheTerminal is a online terminal for fun, it contains lots of different functions and commands. In fact you can SUBMIT YOUR OWN COMMANDS at our github if you are good with computers :).",
rand: () => {
return Math.floor(Math.random() * 1000 ) + 1;
},
//"rand --info": "Will generate a random number between 1 and 1000",
}
and I had to change the line
this.outputs.unshift(this.commands[this.state.inputValue]);
to
this.outputs.unshift(this.commands[this.state.inputValue]());
silly mistake, hopefully this helps someone who has a somewhat similar problem, though I think it would be unlikely.