how to code the utility functions in React without cluttering the component it'self - reactjs

I have build this small react app with 3 components showing weatherforcaset from an api.
I am not sure how I have handled the utils functions in a separate node module is the correct way as it's pretty adhoc, mainly to do with the date handling and creating another 5 arrays with the data received from the api.
https://github.com/c-science1/weatherForecastReact/
Please can someone advise me if there is a better way if doing the same?
Many Thanks!

It looks like you can minimize the code in the function while keeping the DRY principle. You should refactor the following method:
this.createNewLists = function (dayName, itemP){
let itemDate = new Date(itemP.dt_txt);
let currentDate = new Date();
let nextDate = new Date(); ;
if (itemDate.getDate() == currentDate.getDate() ){
this.day1.push(itemP);
this.day1Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day2.push(itemP);
this.day2Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day3.push(itemP);
this.day3Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day4.push(itemP);
this.day4Name = dayName;
}
nextDate.setDate(nextDate.getDate() + 1);
if (itemDate.getDate() === nextDate.getDate()){
this.day5.push(itemP);
this.day5Name = dayName;
}
}
The pattern in this function are repeating them and you can better organize this code.
You can iterate from 1 to 5 (for the weekdays) and minimize your code and keep clean code and the DRY principle.

I would say for the size of your app it is just fine. Regarding things I might do differently, I would not use utils function wrapper, since you already have module serving as namespace.
As for weatherImage function I would probably put it into component file that uses it. If more than one component uses it, I would probably put it into components/common.js. createNewLists would probably also go to this module.

Related

Best practice testing return value from a function with Jest + React Testing Library

I have a very simple salary calculator function that receives as parameters input values ​​inside a form and that in the end returns the result with its calculations.
Logic Function
export function calcAnnualSalary(
monthlySalary: string,
healthPlan?: string,
transpostationTicket?: string,
mealTicket?: string,
valueSaturday?: boolean
) {
const annualSalary =
parseFloat(monthlySalary.replace(/\./g, '').replace(',', '.')) * 12
const thirteenth = parseFloat(
monthlySalary.replace(/\./g, '').replace(',', '.')
)
const extraHoliday =
parseFloat(monthlySalary.replace(/\./g, '').replace(',', '.')) / 3
const totalAnnualCrude = annualSalary + thirteenth + extraHoliday
return {
annualSalary,
thirteenth,
extraHoliday,
totalAnnualCrude,
}
}
Testing
With that, I created a very simple test with hardcoded values, I would like to know if this is the best practice to test function calculation logic. To avoid hardcoded for example, I should get the value inside the form, what would it suggest?
import {CalcAnnualSalary} from '~src/components/app/Calculators/CalcAnnualSalary'
import * as Calc from '~src/utils/calculators/'
import * as Lib from '~src/utils/testing-library'
describe('CalculatorAnnualSalary', () => {
it('expect return gross annual salary', () => {
const {annualSalary} = Calc.calcAnnualSalary('1.000,00')
expect(annualSalary).toEqual(12000)
})
})
In the test, you should provide the test double and test data as simply as possible. That reduces the complexity and facilitates testing.
Whether you use static data or dynamically generated test data, keep it simple. With simple test data, you can also more easily predict the desired results.
The test is predictable, when writing test cases, you should provide the desired result before running the test cases, if your input is complicated, the desired result is difficult to calculate, you need to execute the code in your brain with this data.
Use simple test data to test every code branch, the logical fragment of a function.

'setState' of useState hook occurs twice by one call

There is a board with squares their value relies on an array, it is handled with useState hook. Every click should raise the value by one, but unfortunately, it raises it by two (except the first click).
My questions are:
(1) Why is it happen, (2) how to avoid it, and, in general, (3) is there a better way to handle such an array with hooks.
let emptyBoard = Array.from({ length: parseInt(props.rows, 10) }, () =>
new Array(parseInt(props.columns, 10)).fill(0)
);
const [squaresValues, setSquaresValue] = useState(emptyBoard);
function onClick(id) {
const [rowIndex, cellIndex] = id;
console.log("the " + id + " square was clicked");
setSquaresValue(prevValues => {
let newBoard = [...prevValues];
console.log("before: " + newBoard[rowIndex][cellIndex]);
newBoard[rowIndex][cellIndex] = newBoard[rowIndex][cellIndex] + 1;
console.log("after: " + newBoard[rowIndex][cellIndex]);
return newBoard;
}
);
}
The log:
the 3,0 square was clicked
before: 0
after: 1
the 3,0 square was clicked
before: 1
after: 2
before: 2
after: 3
As can be seen, from the second click the value is raised twice by every click.
You were still mutating state, if you have pure components then they won't re render when mutating. Doing the full state copy with JSON.parse is a bad idea if you have pure components because everything will be re rendered.
let newBoard = [...prevValues];
newBoard[rowIndex] = [...newBoard[rowIndex]];
newBoard[rowIndex][cellIndex] =
newBoard[rowIndex][cellIndex] + 1;
As mentioned by Udaya Prakash in the comment above, it's being called twice to make sure your setState is independent and idempotent. So, if I understand correctly, it being called twice is not a bug, but your values being changed the second time is.
Here's Dan Abramov's comment from the same GitHub issue:
It is expected that setState updaters will run twice in strict mode in development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
We can fix it by deep-copying your prevValues instead of shallow-copying with spread operator. As you might already know, there are multiple ways to deep copy your object, we could go with JSON.parse(JSON.stringify(...) for now, which you can replace with your favorite kind from here
setSquaresValue(prevValues => {
let newBoard = JSON.parse(JSON.stringify(prevValues)); // <<< this
console.log("before: " + newBoard[rowIndex][cellIndex]);
newBoard[rowIndex][cellIndex] = newBoard[rowIndex][cellIndex] + 1;
console.log("after: " + newBoard[rowIndex][cellIndex]);
return newBoard;
});
I've kind of simulated it in codesandbox here if you want to play around.

create an array of the first n integers in google app script

Following this answer for an analogous question for vanilla JavaScript, I've tried to create an array of integers from 1 to 20 with the following code:
var k=Array.from(new Array(20), (x,i) => i + 1);
but I get Syntax error. Evidently Google App Script does not support ES6.
Is there any modern alternative to for ... loop?
You want to create an array including the number in each element using Google Apps Script.
It's like [1.0, 2.0, 3.0,,, 20]
If my understanding is correct, how about this workaround? Please think of this as just one of several workarounds. In this workaround, I used Array.apply().
Sample script 1:
var res = Array.apply(null, Array(20)).map(function(_, i) {return i + 1});
console.log(res); // or for Google Apps Script, Logger.log(res)
Sample script 2:
In the current stage (2022), the following sample script can be also used.
var res = [...Array(20)].map((_, i) => i + 1);
console.log(res);
Note:
For example, when Array.apply(null, Array(3)) and [...Array(3)] are run, an array of [undefined,undefined,undefined] is created. In this script, values are put in each element using map(). If you want to fill an array by a value, you can also use Array(3).fill("sample").
Above script can be worked with Google Apps Script.
If you want to use Logger.log(), please replace console.log(res) to Logger.log(res).
References:
Function​.prototype​.apply()
map()
Benchmark: Loop for Array Processing using Google Apps Script
Issue:
The main issue is that arrays created using new Array(length) is sparse and have elements that's never set and most array methods don't work on it.
Solution:
It's possible to create a dense array from sparse array using apply. Then, it's easy to get indexes of that array using Object.keys()
Snippets:
//using concat
function test11(){
Array.prototype.range = function(len){
return Object.keys(Array.prototype.concat.apply([],new Array(len)))//sparse to dense
}
Logger.log([].range(16))
}
//using push
function test12(){
Array.prototype.range = function(len){
var out = [];
Array.prototype.push.apply(out,new Array(len))
return Object.keys(out);
}
Logger.log([].range(15))
}
No. for/while loop is still best (performant) even on modern browsers. A small function can be used :
function range(n) { var a = []; while(n) a[n - 1] = n--; return a }
console.log( range(5) )

How to add a module to Angular in Pencilblue?

So I'm building this Pencilblue website. Pencilblue is based on the MEAN stack.
I'm trying to get a search function going. I need to declare a module.
Pencilblue does it like this:
ClientJs.getAngularController = function(objects, modules, directiveJS) {
if(!util.isArray(modules) || modules.length > 0) {
modules = ['ngRoute'];
}
var angularController = 'var pencilblueApp = angular.module("pencilblueApp", ' + JSON.stringify(modules) + ')';
So the 2nd line is telling me the modules are loaded from somewhere else, unless there are none, in which case, the modules = ['ngRoute']; should be loaded.
What I came up with is this:
ClientJs.getAngularController = function(objects, modules, directiveJS) {
if( modules.length > 0) {
modules = ['ngRoute', 'elasticui'];
}
var angularController = "var pencilblueApp = angular.module('pencilblueApp', " + JSON.stringify(modules) + ").constant('euiHost', 'localhost:9200')";
While this works, I'm not sure it's an orthodox way of doing it and I might need to add others in the future. I'd really appreciate if someone could help out and tell me the right way to add this ['elasticui'] module in Pencilblue, together with the last part, the .constant('euiHost', 'localhost:9200')";
I'm adding ElasticUI to my project, and the only thing that I had problems with was adding this step: angular.module('yourApp', ['elasticui']).constant('euiHost', 'http://localhost:9200');
It's rather trivial to do it in a MEAN stack or plain Angular.js, but it's quite confusing in Pencilblue.
Would really appreciate a detailed response on how to do this the proper way. Thanks.

image array and .src - image not changing

I have created an array which is being used to store a series of .gif images and I'm just trying to test everything out by using document.getElementById to change the .src value but when I change it and load the page the image stays the same as it was before.
function setImage()
{
var images = new Array();
images[0] = anemone.gif;
images[1] = ball.gif;
images[2] = crab.gif;
images[3] = fish2.gif;
images[4] = gull.gif;
images[5] = jellyfish.gif;
images[6] = moon.gif;
images[7] = sail.gif;
images[8] = shell.gif;
images[9] = snail.gif;
images[10] = sun.gif;
images[11] = sunnies.gif;
images[12] = whale.gif;
var slots = new Array();
slots[0] = document.getElementById("slot" + 0);
slots[1] = document.getElementById("slot" + 1);
slots[2] = document.getElementById("slot" + 2);
slots[0].src = "snail.gif";
document.getElementById('slot0').src = images[0];
alert(images.length);
}
I can't understand why the image wont change, but I know it has to be something very simple. I've been wasting hours trying to get this one thing to change but nothing works. can anyone please point out the error of my ways?
There are a couple of issues with your code:
Your filenames need to be Strings, so they'll have to be quoted (also you can simplify the Array creation):
var images = ['anemone.gif', 'ball.gif', 'crab.gif', 'fish2.gif', 'gull.gif', 'jellyfish.gif', 'moon.gif', 'sail.gif', 'shell.gif', 'snail.gif', 'sun.gif', 'sunnies.gif', 'whale.gif'];
Also make sure you are getting your slot-elements right, quote all the attributes like:
<img id="slot0" class="slot" src="crab.gif" width="120" height="80">
When you create the slots-Array you can do it like this (no need to concat the ID string):
var slots = [document.getElementById('slot0'), document.getElementById('slot1'), document.getElementById('slot2')];
Finally make sure you call your function when the document has loaded / the DOM is ready. If you don't want to use a framework like jQuery your easiest bet is probably still using window.onload:
window.onload = setImage; //note that the parens are missing as you want to refer to the function instead of executing it
Further reading on Arrays, window.onload and DOMReady:
https://developer.mozilla.org/de/docs/DOM/window.onload
https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array
javascript domready?

Resources