Saturday, April 30, 2022

React and uncontrolled component experiment

My project uses React for the client side code.  Normally, changes in the display options dropdown are made as soon as the user makes them so there is no need for "Cancel" or "Apply" buttons. There is, however, a section of input fields that must be canceled or applied as a set, which is where a recent bug appeared.

Environment:
react@17.0.1

The "Cancel" button (and closing of the dropdown) for the set of input fields was not restoring settings prior to the settings being applied.  This was confusing and erroneous because the user thinks the settings were applied even if they may not have been.

Let's name the parent component <Result> and the child component <DisplayOptions>.  The reason why this occurred is because the set of input elements were all React controlled components where state was stored in the parent <Result> component and an event handler was called for every state update.  

What needed to happen was the set of input elements needed to be saved to the parent as a set when the "Apply" button was clicked and when the "Cancel" button was clicked it needed to restore any settings that hadn't been applied.

There were two solutions that I looked into to solve this:   

  1. Change input elements to uncontrolled components.  After hitting apply, call a props function to save it back up to the parent.  
    • Outcome - I've noted down the reasons why I couldn't go with this solution:
      • If <DisplayOptions> had no child components that depended on the changing value of an uncontrolled component, I think it would work.  But if there is a child component that is dependent on an uncontrolled component's value changing, there's no way to force a prop or state change for the child component to re-render.
      • Extra care is needed to keep track of checkbox values with input[inputReferenceName].current.checked and text values with input[inputReferenceName].current.value.
      • For any values that are inherently a number, the uncontrolled component's current value is always a string.  So parsing to an int would need to happen each time its value is needed.
      • Only after the initial render will any form input elements have the handle to the React reference declared in the component's constructor.  This made for extraneous code used throughout to either use the dropdown's component React reference to the input element's current value when it was defined, or the props value of the input element sent from the parent to set the initial value of the input elements.
  2. Keep the input elements React controlled, but have a local copy saved in state for the <DisplayOptions> component.  Each time <DisplayOptions> is opened the constructor is called, so this was the location to set the props values of the input elements sent from the parent to local state.  After clicking apply, call a props function to save it back up to the parent.  This way the next time <DisplayOptions> is opened, its constructor would be called again to set the current value of the input elements.
    • Outcome - This ended up being a simple solution and best solution for the use case.

Thursday, March 31, 2022

PostgreSQL - query for array elements inside json data type

My application has a table column "params" that is a JSON data type.  As a side note, it's a JSON data type (vs JSONB) because the ordering of keys must stay consistent because this column is hashed and used as the key for caching.  The contents of the "params" column has object values that are strings, numbers, and arrays.

Environment: 

PostgreSQL 12.9

Take a params value that looks like:

{
    "filterId": "a1cef72a-9d84-4cfc-9690-9f4d772f446c",
    "name": "cherryshoetech",
    "active": true,
    "priority": 2,
    "areas": [{
            "type": "custom",
            "startedInside": true,
            "endedInside": false,
            "order": 0
        }
    ],
}

To query for a specific value in the "areas" array, make use of the json_array_elements function.  It will expand a JSON array to a set of JSON values.  Therefore, it will return one record for each element in the array.

The following will return a record for each json array element in analysis.params.areas:

select id, created_tstamp, cherryshoeareas
from cherryshoe.analysis analysis, json_array_elements(analysis.params#>'{areas}') cherryshoeareas;

Once you filter it down with the where clause, it will only return the record that satisfies that criteria. Below are examples for filtering by a string, integer, and boolean:

select id, created_tstamp, cherryshoeareas
from cherryshoe.analysis analysis, json_array_elements(analysis.params#>'{areas}') cherryshoeareas
where cherryshoeareas ->> 'type' = 'custom';

select id, created_tstamp, cherryshoeareas
from cherryshoe.analysis analysis, json_array_elements(analysis.params#>'{areas}') cherryshoeareas
where (cherryshoeareas ->> 'order')::integer = 0;

select id, created_tstamp, cherryshoeareas
from cherryshoe.analysis analysis, json_array_elements(analysis.params#>'{areas}') cherryshoeareas
where (cherryshoeareas ->> 'startedInside')::boolean is true
and (cherryshoeareas ->> 'endedInside')::boolean is false;

Helpful Articles:

https://www.postgresql.org/docs/12/datatype-json.html

https://stackoverflow.com/questions/22736742/query-for-array-elements-inside-json-type

https://www.postgresql.org/docs/12/functions-json.html

Monday, February 28, 2022

Leaflet custom map panes

I recently needed to have certain Leaflet path elements be drawn last (on top) on the map but below markers and pop-ups.  Since SVG doesn't use z-index but rather rendered based order of elements, I needed a guaranteed solution to make this work.

Leaflet map panes were the solution!  It's a feature available as of Leaflet 1.0.0 which allows for customization of this order.  Creating a custom map pane generates a <div> that can be styled with CSS z-index.

const CUSTOM_CS_PANE = "cherry-shoe-custom";

// create a custom map pane for area boundaries so it can be above leaflet-overlay-pane but below leaflet-marker-pane
this.map.createPane(CUSTOM_CS_PANE); // generate areas const options = { pane: CUSTOM_CS_PANE, className: "cs" }; areasToDisplay.forEach(areaToDisplay => { const area = L.geoJSON(areaToDisplay.geoJSON, options); this.map.addLayer(area); });

SCSS classes:

.leaflet-cherry-shoe-custom-pane {
  z-index: 550;
}
.cs {
    stroke: black;
    stroke-width: 3;
    stroke-opacity: 1;
    fill: none;
}

HTML looks like:

<div class="leaflet-pane leaflet-cherry-shoe-custom-pane" style="z-index: 650;">
  <svg pointer-events="none" class="leaflet-zoom-animated" width="1306" height="923" viewBox="-109 -77 1306 923" style="transform: translate3d(-109px, -77px, 0px);">
    <g>
      <path class="cs leaflet-interactive" stroke="#3388ff" stroke-opacity="1" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="#3388ff" fill-opacity="0.2" fill-rule="evenodd" d="M1200 -80L1199 -62L1191 -59L1186 -60L1176 -55L1153 -35L1142 -31L1137 -25L1136 -20L1131 -15L1121 -13L1110 -6L1109 8L1112 17L1111 22L1106 29L1088 31L1078 40L1060 46L1041 45L1034 47L1008 68L994 59L969 51L950 50L931 53L922 56L907 64L897 73L885 87L879 98L877 106L577 107L-112 106L-112 -80z"></path><path class="cs leaflet-interactive" stroke="#3388ff" stroke-opacity="1" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" fill="#3388ff" fill-opacity="0.2" fill-rule="evenodd" d="M531 339L537 342L539 346L541 345L541 343L547 343L551 338L557 338L564 341L569 334L535 316L536 271L600 271L601 344L607 351L613 354L658 371L667 373L673 350L689 327L669 313L673 306L680 303L683 300L679 291L679 287L674 279L678 273L676 270L667 265L659 255L659 249L656 246L656 236L653 233L654 230L652 230L648 223L646 224L645 221L641 221L639 217L636 216L638 208L634 210L631 207L627 207L623 203L620 204L620 202L618 202L614 198L610 200L604 194L604 192L600 190L601 183L598 180L598 176L596 176L595 169L593 169L592 166L590 166L591 165L589 161L590 160L588 154L587 106L523 106L509 107L507 109L482 196L478 212L479 217L475 219L470 229L472 231L469 232L471 234L475 234L471 237L470 236L470 242L468 242L468 244L472 246L471 247L474 249L476 248L475 250L473 250L472 255L474 257L472 259L476 263L474 263L470 267L470 271L476 279L474 278L473 283L476 286L476 288L474 288L480 291L485 298L488 297L491 300L493 296L495 296L496 299L498 297L505 298L506 299L504 303L511 306L511 308L508 309L508 315L505 320L506 322L517 330L519 330L524 335L524 337L531 339z">
</path> </g> </svg>< /div>

Other helpful articles:

https://github.com/Leaflet/Leaflet/blob/v1.0.0/dist/leaflet.css#L84

https://stackoverflow.com/questions/39767499/how-to-set-the-zindex-layer-order-for-geojson-layers

https://jsfiddle.net/3v7hd2vx/90/

Tuesday, January 18, 2022

EFTPS Individual Payment Phone Number

I wanted to let anyone that may stumble upon this blog that if you're trying to pay your federal estimated taxes through the EFTPS phone system and you do not yet have your PIN to call 1-800-316-6541.  This number is not published on the EFTPS website, but I found it in an old irs.gov newsletter and is the "individual payment line / individual customer service" number (one of the wonderful operators told me that's the label it's listed as).  It's also listed as the number to call if you have a question on a transaction with the pamphlet that comes in the mail when you get your PIN.



I used to have a working EFTPS account but haven't logged into it for close to two years; apparently, EFTPS had a system password update in September of 2019.  Passwords expire about every 13 months, and any inactive account is purged at 18 months.  The safe rule of thumb to keep your account active is to log in yearly and change your password.

Two helpful numbers:

  • Listed as individual payment line / individual customer service: 1-800-316-6541
  • Bypass automated number and contact operator directly (I haven't tried this personally): 1-800-991-2245

Helpful Article:

https://tscpafederal.typepad.com/blog/2019/08/eftps-system-password-update-.html


Wednesday, December 29, 2021

JS separate out / remove attributes from an Object

I needed to separate out attributes to save for query parameters, and other settings separately.  So instead of looping through filters twice to remove what I needed to populate query parameters and other settings, do it once:

const filters = [
  {
    type: "type1",
    ids: [22,33,44,55],
    other1: { testdata: "hi" },
    other2: true,
    other3: false,
    other4: ["you"],
  },
  {
    type: "type2",
    ids: [66,77,88],
    other1: { testdata: "howdy" },
    other2: false,
    other3: true,
    other4: ["there"],
  }
];
const otherSettings = [];
const queryParams = [];
filters.forEach(filter => {
  const { ids, type, ...otherSetting } = filter;
  const queryParam = {
    ids,
    type
  };
  otherSettings.push(otherSetting);
  queryParams.push(queryParam);
});
console.log(`queryParams:${JSON.stringify(queryParams)}, otherSettings:${JSON.stringify(otherSettings)}`);

Output

queryParams: [{
        "ids": [22, 33, 44, 55],
        "type": "type1"
    }, {
        "ids": [66, 77, 88],
        "type": "type2"
    }
], otherSettings: [{
        "other1": {
            "testdata": "hi"
        },
        "other2": true,
        "other3": false,
        "other4": ["you"]
    }, {
        "other1": {
            "testdata": "howdy"
        },
        "other2": false,
        "other3": true,
        "other4": ["there"]
    }
]

Monday, November 29, 2021

Save redis key value to file

Some of my redis key values are very large so it's easier for me to query for one key's value and view it in a file.

Environment:
CentOS Linux release 7.3.1611 (Core)
redis server 3.2.12
redis-cli 3.2.12

If I have a key named "CST_9527d72b05eb713c20465d6bc5022775bbe3a4ffb99b261a8231e967fe1585ce" enter in either command to save the key's value to cst.txt

redis-cli --scan --pattern "CST_9527d72b05eb713c20465d6bc5022775bbe3a4ffb99b261a8231e967fe1585ce" | xargs redis-cli mget > cst.txt
echo 'GET CST_9527d72b05eb713c20465d6bc5022775bbe3a4ffb99b261a8231e967fe1585ce' | redis-cli > cst.txt

Helpful Article:

https://stackoverflow.com/questions/44826691/how-to-get-value-by-redis-cli-keys

Saturday, October 30, 2021

How to fix Node.js PayloadTooLargeError: request entity too large

Displaying results on a visualization when there were many areas that needed to be displayed on a map caused a HTTP 413 PayloadTooLarge error.  The HTTP request in question had 20,000+ areas defined that made a request payload of 150+ kb.

Things fixed:

The first problem was that Node's HTTP request body parser default request payload limit is 100kb. I increased it to 1mb, as I don't want to arbitrarily make it any bigger. NOTE: nginx didn't have any request payload limits configured.

The second thing was that since Node express version 4.16.0, no longer needed a separate body-parser library, I could use express's internal one.

OLD for Node 8.11.3

const express = require("express");
const bodyParser = require("body-parser");

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

NEW for Node 14.17.1

const express = require("express");

const app = express();
// The default express max request limit is 100kb, increase it
const maxRequestBodySize = '1mb';
app.use(express.json({limit: maxRequestBodySize}));
app.use(express.urlencoded({limit: maxRequestBodySize}));

Helpful Articles:

https://stackoverflow.com/questions/19917401/error-request-entity-too-large

https://stackoverflow.com/questions/31967138/node-js-express-js-bodyparser-post-limit