Javascript Image and GIF Optimization and Compression

So I am generating GIF images by taking imageData stored in an array, undoStack[], and using gif.js to compile them into a gif. I found out that the images are coming out quite large in comparison to what I would like them to be…. See the two attached files. Large.gif being the one that I am generating and small.gif being the one that I am putting into freeconvert.com to compress the file….. There is a 92% reduction. I’ve tried messing with the options in gif.js to optimize the gif generation, but I’ve been unsuccessful. Does anyone have any suggestions to decrease the size of my gifs without losing too much quality? Not sure if I need to somehow optimize the imageData before gif generation, or if there is something I can do after the gif is created to reduce the size like Freeconvert is doing…

THank you in advance!

Here is the function in my web application:

async function saveGIF() {
    return new Promise((resolve, reject) => {
        const gif = new GIF({
            workers: 5,
            quality: 10,
            width: 1000,
            height: 1000,
            dither: false,
        });

        // Calculate total duration and frame interval
        const maxDuration = 5000; // Maximum duration in milliseconds (5 seconds)
        const targetFPS = 30;
        const maxDelayTime = 10;

        // Ensure that there are at least two frames
        const minFrames = 2;

        // Calculate the maximum number of frames based on the duration and target FPS
        const maxFrames = Math.floor((maxDuration / 1000) * targetFPS);

        // Calculate the delay time for each frame
        let delayTime = Math.ceil(maxDuration / Math.min(undoStack.length, maxFrames));
        if (delayTime > maxDelayTime) {
            delayTime = maxDelayTime;
        }
        let gifLoad = 0;

        // Keep track of the last frame added to the GIF
        let lastFrameAdded = null;

        for (let i = 0; i < Math.min(undoStack.length, maxFrames); i++) {
            const index = undoStack.length > maxFrames ? Math.round((undoStack.length - 1) * i / (maxFrames - 1)) : i;
            const imageData = undoStack[index];

            // Compare with the last frame added
            if (i === 0 || hasEnoughDifference(imageData, lastFrameAdded)) {
                gif.addFrame(imageData, { copy: true, delay: delayTime });
                lastFrameAdded = imageData; // Update lastFrameAdded
            }
        }

        // Add an additional second with just the last frame
        gif.addFrame(lastFrameAdded, { copy: true, delay: 1000 });

        gif.on('finished', function (blob) {
            const url = URL.createObjectURL(blob);
            const link = document.createElement('a');

            if (submitStatus === false){
                link.href = url;
                link.download = 'doodle.gif';
                link.click();
            }
            
            URL.revokeObjectURL(url);

            // Resolve the Promise with both the GIF blob and its URL
            resolve({ blob, url });
        });

        gif.on('error', function (errMsg) {
            console.error('GIF rendering error:', errMsg);

            // Reject the Promise with the error message
            reject(errMsg);
        });

        gif.render();
    });
}

My GIF 1.9mb

Same GIF after put into Freeconvert.com

I’ve tried changing the Quality and dithering settings in Gif.js options but that has made little difference. I’ve tried using other libraries such as compressorjs which actually seemed to make my images even larger.

TypeError: messageList.map is not a function

I have a .cjs file that, when run with node, returns all urls it is crawling(that part works fine) but has a few errors I need to fix.

Here are two errors of the same kind:

    Error generating and outputting text: TypeError: messageList.map is not a function                                                                             
        at /home/legomyego/scraping/openai/node_modules/@langchain/core/dist/language_models/                                chat_models.cjs:243:72                                                
    at Array.map (<anonymous>)                                                                                                                                 
    at ChatOpenAI.generate (/home/legomyego/scraping/openai/node_modules/@langchain/core/dist/        language_models/chat_models.cjs:243:39)                          
    at generateAndOutputText (/home/legomyego/scraping/openai/aiScrape6.cjs:60:41)                                                                             
    at crawl (/home/legomyego/scraping/openai/aiScrape6.cjs:37:15)                                                                                             
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)                                                                              
    at async /home/legomyego/scraping/openai/aiScrape6.cjs:85:9                                                                                                                                                                                                                                                         
    Error generating and outputting text: TypeError: messageList.map is not a function                                                                             
    at /home/legomyego/scraping/openai/node_modules/@langchain/core/dist/language_models/    chat_models.cjs:243:72                                                
    at Array.map (<anonymous>)                                                                                                                                 
    at ChatOpenAI.generate (/home/legomyego/scraping/openai/node_modules/@langchain/core/dist/     language_models/chat_models.cjs:243:39)                          
    at generateAndOutputText (/home/legomyego/scraping/openai/aiScrape6.cjs:60:41)                                                                             
    at CsvParserStream.<anonymous> (/home/legomyego/scraping/openai/aiScrape6.cjs:95:23)                                                                       
    at CsvParserStream.emit (node:events:514:28)                                                                                                               
    at CsvParserStream.emit (/home/legomyego/scraping/openai/node_modules/@fast-csv/parse/build/                               src/CsvParserStream.js:50:23)                                  
    at endReadableNT (node:internal/streams/readable:1376:12)                                                                                                  
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21)   

I think that this function below is the reason for the error. This is because “messages” is of type “object” and not an array before sent to ‘const generatedText’. However changing it to an array did not work.
Here is the function:


     async function generateAndOutputText(userPrompt, context) {
    try {
        const messages = [
            { role: "system", content: "You are a helpful assistant." },
            { role: "user", content: userPrompt },
            { role: "assistant", content: context },
        ];
    console.log(typeof messages);
        const generatedText = await llm.generate(messages);

        console.log(generatedText);

        // Save generated text to a CSV file
        fs.writeFile('generated_text.csv', generatedText, (err) => {
            if (err) {
                console.error('Error saving generated text to file:', err);
            } else {
                console.log('Generated text saved to generated_text.csv');
            }
        });
    } catch (error) {
        console.error('Error generating and outputting text:', error);
    }
    }

Finally here is a pastebin of the full code: pastebin(.)com/RGDhqLdW

Javascript Intl.NumberFormat for fr-ch locale and currency CHF wrong output in new safari browser

For the below code in safari version 17 getting the output has “CHF 123 456,79” but for version safari version 16 it is giving the output “123 456,79 CHF”
, in chrome browser it is giving the output”123 456,79 CHF” dose anyone know why

console.log(
  new Intl.NumberFormat('fr-ch', { style: 'currency', currency: 'CHF' }).format(
    number,
  ),
);

Alpine.js ‘paginationData is not defined’ Error in Laravel Jetstream Component

I’m working on a Laravel Jetstream project with Alpine.js and Livewire. I’m encountering an error in my console indicating that paginationData is not defined, despite having imported it correctly into my app.js file and having declared it in a separate module.

Here’s my current setup:

1. Import in app.js:

import Alpine from 'alpinejs';
import paginationData from './alpine/pagination-stepper.js';

Alpine.data('paginationData', () => paginationData);
Alpine.start();
console.log('Alpine started');

2. Definition of paginationData in pagination-stepper.js:

const paginationData = {
    currentPage: 1,
    perPage: 5,
    totalCount: 10,
    get paginatedData() {
        const start = (this.currentPage - 1) * this.perPage;
        const end = start + this.perPage;
        return Array.from({ length: this.totalCount }, (_, i) => i + 1).slice(start, end);
    },
    nextPage() {
        if (this.currentPage < Math.ceil(this.totalCount / this.perPage)) {
            this.currentPage++;
        }
    },
    prevPage() {
        if (this.currentPage > 1) {
            this.currentPage--;
        }
    }
};

export default paginationData;

3. Usage in welcome.blade.php:

<section x-data="paginationData">
    <div>
        Current Page: <span x-text="totalCount"></span>
    </div>
</section>

It does show the current page number

enter image description here

4. Error in Console:

Despite showing the page number, indicating that the code gets loaded successfully, I still get an error.

enter image description here

I’ve tried the following troubleshooting steps:

  • Verified that the import is correct and that paginationData is properly defined.
  • Cleared the Laravel cache with php artisan view:clear and php artisan cache:clear.
  • Restarted the development server.
  • Checked for conflicts with Livewire.

Despite these attempts, the error persists. What could be causing this issue, and what should I check or adjust to resolve it? Any insights or suggestions from the Alpine.js community would be greatly appreciated.

Show permission pop up for clipboard

I’m working on app that requires the user’s permission to read/write his clipboard. I want to block the entrance to a part of my website if they haven’t granted those permissions. Also, if they haven’t granted permission display a button that when clicking it shows the small pop up at the top left corner of the browser to grant the permission. I already tried using this but it doesn’t work:

async function checkClipboardPermissionAndRequest() {
  try {
    const result = await navigator.permissions.query({ name: 'clipboard-read' });
    
    if (result.state !== 'granted') {
      const permissionRequest = await navigator.permissions.request({ name: 'clipboard-read' });
      if (permissionRequest.state === 'granted') {
        console.log('Clipboard read permission granted.');
      } else {
        console.log('Clipboard read permission denied.');
      }
    } else {
      console.log('Clipboard read permission already granted.');
    }
  } catch (error) {
    console.error('Error checking clipboard permission:', error);
  }
}

// Call the function to check clipboard permissions and request if necessary
checkClipboardPermissionAndRequest();

This was just for web console testing, but I want to implement it in my Next.js app.

React image upload re-rendering uploaded images when adding/deleting

I have a React code that lets you add image files, preview, delete on click and add more. I’m happy with the functionality but I noticed some performance issues.

function App() {
  const [selectedFiles, setSelectedFiles] = React.useState([])

  function GenerateGuid() {
    return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c => {
      const randomValue = crypto.getRandomValues(new Uint8Array(1))[0];
      const shiftedValue = (randomValue & 15) >> (+c / 4);
      return shiftedValue.toString(16);
    });
  }

  const handleImageDelete = (id) => {
    const updatedFiles = selectedFiles.filter((file) => file.id !== id);
    setSelectedFiles(updatedFiles);
  };

  const handleChange = (e) => {
    const files = Array.from(e.target.files)
    const filesObject = files.map(file => {
      return (
        { id: GenerateGuid(), fileObject: file }
      )
    })
    setSelectedFiles([...selectedFiles, ...filesObject])
  }

  return (
    <div className='App'>
      <h1>Hello React.</h1>
      <h2>Start editing to see some magic happen!</h2>
      <input type="file" onChange={handleChange} multiple />
      {selectedFiles.map(
        (file, index) => {
          return (
            <img
              key={file.id}
              onClick={() => handleImageDelete(file.id)}
              style={{ height: "5rem", backgroundColor: "black" }}
              src={URL.createObjectURL(file.fileObject)}
              alt={"training spot"}
            />
          )
        }
      )}
    </div>
  );
}
ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Works fine in small file amounts, but let’s review the following scenario.

  • Add 10 images each ~2MB size, so we’ve uploaded ~20MB of data
  • Click on one image to delete it
  • 9 images re-render and network tab shows 9 images reloaded again, so 19 requests in total to complete this operation.

Network tab showing the problem

Is there a clever way to prevent remaining images from reloading?

So far just testing in the sandbox and looking for solution.

autoComplete.js How to pass a search query for data querying on input change

Good day

I’m using this autocomplejs library so intead of loading existing data I’m getting data from an endpint and I pass a query to get a specific data, Everything works but I can’t seem to query on input change and when I check the documentation it has an example but don’t get it or its not complete.

I’m passing a search query on that endpoint
like below

<div wire:ignore x-data="{ value: @entangle($attributes->wire('model')) }" x-init="new autoComplete({
    selector: '#{{ $id }}',
    placeHolder: '{{ $attributes->has('placeholder') ? $attributes->get('placeholder') : 'Please enter your address...' }}',
    data: {
         // I don't know how to call this query on input change so I tried getting the input value like  
        
        src: async (query) => {
            try {
                // const query = document.querySelector('#{{ $id }}').value; and removed the quiry param but its not working still
                const source = await fetch(`/places/autocomplete/${query}`);
                const data = await source.json();
                console.log(JSON.stringify(data));
                return data;
            } catch (error) {
                console.log(JSON.stringify(error));
                return error;
            }
        },
        keys: ['description'],
        cache: true,
    },
    searchEngine: '{{ $attributes->has('searchEngine') ? $attributes->get('searchEngine') : 'loose' }}',
});" x-on:selection="value = $event.target.value"
    class="!w-full relative">
    <input type="search" {{ $attributes->whereDoesntStartWith('wire:model') }} id="{{ $id }}"
        x-bind:value="value"
        class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block !w-full p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
        autocomplete="off" tabindex="1">
</div>

@once
    <link rel="stylesheet"
        href="https://cdn.jsdelivr.net/npm/@tarekraafat/[email protected]/dist/css/autoComplete.01.min.css">

    <script
        src="https://cdnjs.cloudflare.com/ajax/libs/tarekraafat-autocomplete.js/10.2.7/autoComplete.min.js?id=<?= rand(0, 1000 - 1) ?>"
        data-navigate-track></script>

    <script>
        let autocompletInput = document.getElementById("autoComplete");
        autocompletInput.addEventListener("selection", function(event) {
            const feedback = event.detail;
            const selection = feedback.selection.value[feedback.selection.key];
            autocompletInput.value = selection;
        });
    </script>
@endonce

I saw this from the documentation
The documentation is not explaining further this
image

but I don’t know how I can pass the query.

I need help with passing that query.

I tried this but the input value is not updating

I also tried listing to change event but no luck

 src: async () => {
            try {
                const query = document.querySelector('#{{ $id }}').value;
                const source = await fetch(`/places/autocomplete/${query}`);
                const data = await source.json();
                console.log(JSON.stringify(data));
                return data;
            } catch (error) {
                console.log(JSON.stringify(error));
                return error;
            }
        },

This is for google places autocomplete.

Thanks in advance.

Reinitialize the values of the fields react-hook-form multi step

I’m am working on implementation of a multi step form with react-hook-form but I can’t do that input fields do get initialized with the form data when I return to the previous page.

I want the fields I filled out in that step to be saved when I go back to the previous step.

//EstudianteForm.jsx
function EstudianteForm() {
    const { register, handleSubmit, reset } = useForm();
    const [carrerasDisponibles, setCarrerasDisponibles] = useState([]);
    const { formData, nextStep, prevStep, updateEstudianteData } = useFormContext();

    useEffect(() => {
        const fetchCarreras = async () => {
            try {
                const response = await fetch('http://localhost:8000/carreras');
                if (!response.ok) {
                    throw new Error('Error al obtener las carreras');
                }
                const data = await response.json();
                setCarrerasDisponibles(data);
            } catch (error) {
                console.error('Error:', error);
            }
        };

        fetchCarreras();
    }, []);

    const onSubmit = handleSubmit(async (data) => {
        const estudianteData = {
            nombreEstudiante: data["nombreEstudiante"],
            telefono: data["telefono"],
            correo: data["email"],
            curso: data["curso"],
            carreraInteres: data["carreraInteres"],
            idEquipo: formData.EquipoData.idEquipo,
        };
        await updateEstudianteData(estudianteData);
        nextStep();
        reset();
    });


    return (
        <form onSubmit={onSubmit}>
            <div className='estudiante-form-container'>
                <label> Nombre del estudiante: </label>
                <input type="text" {...register("nombreEstudiante")} />
            </div>

            <div className='estudiante-form-container'>
                <label> Teléfono: </label>
                <input type="text" {...register("telefono")} />
            </div>

            <div className='estudiante-form-container'>
                <label> Correo electrónico: </label>
                <input type="email" {...register("email")} />
            </div>

            <div className='estudiante-form-container'>
                <label> Curso: </label>
                <select className="custom-select" {...register('curso')}>
                    <option value="">Seleccionar</option>
                    <option value="5">5to secundaria</option>
                    <option value="6">6to secundaria</option>
                </select>
            </div>

            <div className='estudiante-form-container'>
                <label> Carreras de interés: </label>
                <select multiple className="custom-select" {...register('carreraInteres')}>
                    <option value="">Seleccionar</option>
                    {carrerasDisponibles.map(carrera => (
                        <option key={carrera.idCarrera} value={carrera.idCarrera}>{carrera.nombre}</option>
                    ))}
                </select>
            </div>
            <div className='next-previouse-buttons'>
                <button type="button" className='previouse' onClick={prevStep}>Anterior</button>
                <button type="submit" >Siguiente</button>
            </div>
        </form>
    );
}

export default EstudianteForm;

import React, { createContext, useContext, useState } from "react";

const FormContext = createContext();

const useFormContext = () => useContext(FormContext);

const FormProvider = ({ children }) => {

    const [currentStep, setCurrentStep] = useState(0);
    const [formData, setFormData] = useState({
        EquipoData: {},
        EstudianteData: [],
    });

    const nextStep = () => setCurrentStep(currentStep + 1);
    const prevStep = () => setCurrentStep(currentStep - 1);

    const updateEquipoData = (data) => {
        setFormData({ ...formData, EquipoData: data });
    };
    const updateEstudianteData = (data) => {
        setFormData({ ...formData, EstudianteData: [...formData.EstudianteData, data] });
    };


    return (
        <FormContext.Provider value={{
            formData,
            updateEstudianteData,
            updateEquipoData,
            currentStep,
            nextStep,
            prevStep,
        }}>
            {children}
        </FormContext.Provider>
    );
};

export { FormProvider, useFormContext };    

I want the fields I filled out in that step to be saved when I go back to the previous step.

How to bundle a UI library within a monorepo?

I’ve spent a good week trying to figure this out and feel like I’m hitting my head against a wall.

I’m using Turbo as a monorepo and have simply used the create-turbo@latest command, detailed here.

The default ui/package works fine, but I don’t want to manually export everything in the package.json as below (source code here):

{
  "name": "@repo/ui",
  "version": "0.0.0",
  "private": true,
  "exports": {
    "./button": "./src/button.tsx",
    "./card": "./src/card.tsx",
    "./code": "./src/code.tsx"
  },
  "scripts": {
    "lint": "eslint . --max-warnings 0",
    "generate:component": "turbo gen react-component"
  },
  "devDependencies": {
    "@repo/eslint-config": "workspace:*",
    "@repo/typescript-config": "workspace:*",
    "@turbo/gen": "^1.12.4",
    "@types/node": "^20.11.24",
    "@types/eslint": "^8.56.5",
    "@types/react": "^18.2.61",
    "@types/react-dom": "^18.2.19",
    "eslint": "^8.57.0",
    "react": "^18.2.0",
    "typescript": "^5.3.3"
  }
}

I’ve checked Steven Tey’s rather amazing Dub.co and wanted to attempt to use TSUP to compile my UI library. I configured everything exactly the same way he did, right down to the tsconfig, but this keeps giving me errors in my web app of the UI library not being found (although it works fine and uses the UI elements). Further to this, it is also totally breaking on some of my UI components. I know they’re all OK as I’ve used them across other projects for years without issue, it’s only in the monorepo do some now break.

I’ve also tried changing my ui library package.json to simply push the entry point to “src/index.tsx” which houses all of my exports/imports. That src/index.tsx looks like this:

import "./globals.css"

export * from "./tw-indicator"

// Shad components
export * from "./ui"

// Folders
export * from "./forms"
export * from "./layouts"
export * from "./typography"
export * from "./icons"

This however gives me errors such as the below:

TypeError: (0 , react__WEBPACK_IMPORTED_MODULE_0__.createContext) is not a function
This error happened while generating the page. Any console logs will be displayed in the terminal window.

This error happened while generating the page. Any console logs will be displayed in the terminal window.
Call Stack
eval
....node_modules.pnpm@[email protected]_@[email protected][email protected][email protected] (4:82)
(rsc)/../../node_modules/.pnpm/@[email protected]_@[email protected][email protected]/node_modules/@radix-ui/react-direction/dist/index.mjs
file:/C:/Code/Test%20Project/testproject/apps/web/.next/server/vendor-chunks/@[email protected]_@[email protected][email protected] (30:1)

It seems that no matter how I configure the package.json of the ui library, tsconfigs, tsup, I always end up with a litany of errors.

Any way I attempt to bundle the UI library just doesn’t work. Has anyone had any success doing this without receiving errors?

JavaScript: convert radio input value to name of variable without using eval() [duplicate]

I’m happy with how my script is working but I’m told I shouldn’t be using eval() – which I’m using twice. Is that true? How would I do that? There are several posts about this but I can’t make them work this situation. (Below is a simplified version of http://www.JBM-Computing.net/Music.shtml?Music=keys)

javascript:

sharpKeys = ['G maj','D maj','A maj','E maj','B maj','F# maj','C# maj']
flatKeys = ['F maj','Bb maj','Eb maj','Ab maj','Db maj','Gb maj','Cb maj']
function newKey(){
  kArray = document.querySelector("input[type='radio'][name='sel']:checked").value
  keys = eval(kArray)
  num = Math.floor(Math.random() * (keys.length - 2))
  key = keys.splice(num,1)
  eval(kArray).push(key)
  document.getElementById('pKey').innerHTML = key
}

html:

<form>
  <input type="radio" id="js" name="sel" value="sharpKeys" checked><label for="js">sharp keys</label>
  <input type="radio" id="jf" name="sel" value="flatKeys"> <label for="jf">flat keys</label>
</form>
<p><button onClick="newKey()">random key</button></p>
<p id="pKey"></p>

(FYI the random selection is such that the current selection will not be chosen in the next 2 clicks.)

How to handle multiple recaptcha in ejs webpage

I having trouble if i having multiple recaptcha field on two different forms issue when i remove recaptcha div from one form the other is working perfectly but having two only takes the second form captcha value.

<div class="col-12 btn-box">
                  <div class="g-recaptcha contact-recaptcha" id="recaptchaElementId" data-sitekey="site_key" data-action="contactForm"></div>
                </div>

this div is present in one contact page form

<div class="col-12 btn-box" style="margin-top: 20px;">
                <div class="g-recaptcha" id="demo-recaptcha" data-sitekey="site_key" data-action="demoRequest"></div>
              </div>

this one in the modal form


Also site key is same for both the div's

<script>
  function setupFormAndRecaptcha(formId, recaptchaElementId, siteKey) {
    // Render the ReCAPTCHA widget and set up the callback
    var recaptchaWidgetId = grecaptcha.render(recaptchaElementId, {
      sitekey: siteKey,
      callback: function (response) {
        console.log("ReCAPTCHA response received: " + response);
        submitFormWithToken(formId, response);
      },
    });

    // Function to handle form submission with ReCAPTCHA token
    function submitFormWithToken(formId, token) {
      const form = document.getElementById(formId);
      if (form.checkValidity()) {
        event.preventDefault();
        event.stopPropagation();
        console.log("Form is valid, submitting with token:", token);
        formSubmitApi(formId, token);
      }
    }
  }
</script>

but i am getting issue like reCAPTCHA couldn’t find user-provided function: onloadCallback i am using cdn like

<script src="https://www.google.com/recaptcha/api.js?onload=onloadCallback&render=explicit" async defer></script>

Element Not Updating in DOM After its React State is Changed

I have a react variable called dataTable managed with useState() that holds a table element, embedded in the HTML in my site as {dataTable}.

However, when I update it with setDataTable(), it’s value does change and I can check that with console.log(), but it doesn’t update in the DOM for some reason.

All other, more simple elements like number variables will update in the DOM when displayed in the same way.

I update the table with:

setDataTable(
  <DataTable
    todos={todos}
    todoLogs={todoLogs}
    deleteTodo={deleteTodo}
    toggleTodo={toggleTodo}
  ></DataTable>
)

The DataTable class looks like:

class DataTable extends React.Component {
  constructor(props) {
    super(props);
    this.todos = props.todos;
    this.todoLogs = props.todoLogs;
    this.deleteTodo = props.deleteTodo;
  }

  render() {
    const data = this.todoLogs;

    // Map over the data to generate table rows
    try {
      const tableRows = data.map((todoLog) => (
        <tr key={todoLog.id}>
          <td>{this.todos.find(x => x.id === todoLog.id).title}</td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d09_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d08_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d07_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d06_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d05_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d04_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d03_04_24}></input></td>
          <td><input type="checkbox" onChange={() => {this.todos.find(x => x.id === todoLog.id)}} checked={todoLog.d02_04_24}></input></td>
          <td><button><i className="material-icons" onClick={() => {this.deleteTodo(todoLog.id)}}>delete</i></button></td>
        </tr>
      ));

      return (
        <table>
          <thead>
            <tr>
              <th>Title</th>
              <th>09/04</th>
              <th>08/04</th>
              <th>07/04</th>
              <th>06/04</th>
              <th>05/04</th>
              <th>04/04</th>
              <th>03/04</th>
              <th>02/04</th>
            </tr>
          </thead>
          <tbody>{tableRows}</tbody>
        </table>
      );
    } catch (error) {
      console.log(error);
      return <p>Table Loading...</p>;
    }
  }
}

Is there something special about updating React components, or HTML elements with parameters that I don’t know?

Thank you.

How to use MDXjs as the body of the text in areas of the webpage?

I’m trying to make a blog website using Vite + React and Tailwind CSS, and MongoDB for the backend at the moment, and I want to use a markdown language like MDX to display the body and title, as well as any other info that might be on a blog page itself. However, I’m not really sure how to integrate MDX in this way, nor how to style the MDX using custom styling with CSS or something similar.

Here is my blogPage at the moment done with Typescript, React, and HTML/CSS.

import dummyBlogs from "../components/dummy-blogs.tsx";
import {useParams} from "react-router-dom";


const BlogPage = () => {
    const { blogID } = useParams();
    const content = dummyBlogs.find((blog) => blog.id === parseInt(blogID));
    return (
        <>
            <div>
                <div className={"flex-grow align-middle"}>
                    <h1 className={"text-center py-64 text-9xl font-extrabold tracking-widest bg-amber-400 mb-10"}>
                        {content.title}
                    </h1>
                </div>
            </div>
            <div>
                <p>
                    {content.body}
                </p>
                <div>
                    Like button (?)
                </div>
            </div>
        </>
    )
}

export default BlogPage;

and here is what dummyBlogs look like

const dummyBlogs = [
    {
        id: 1,
        title: "Lorem ipsum dolor sit amet",
        headerbg: "amber-400",
        body: "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac mauris id magna ullamcorper egestas in non nunc. In at auctor arcu. Suspendisse diam metus, venenatis in quam quis, consectetur blandit mi. Duis lacinia ultrices mattis. Nunc placerat ligula sit amet magna gravida lobortis. Cras finibus ac risus vel sagittis. Vestibulum vel porttitor neque, a consectetur mi. Pellentesque varius semper convallis. Mauris sed elit finibus, luctus lectus ut, fermentum turpis.",
        footnotes: "*Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac mauris id magna",
        views: 123,
    },
    {
        id: 2,
        title: "Another Blog Post",
        headerbg: "blue-400",
        body: "This is another dummy blog post...",
        footnotes: "*Footnote for this blog post",
        views: 456,
    },
    {
        id: 3,
        title: "Another Blog Post",
        headerbg: "blue-400",
        body: "This is another dummy blog post 3...",
        footnotes: "*Footnote for this blog post",
        views: 728,
    },
    {
        id: 2,
        title: "Another Blog Post",
        headerbg: "blue-400",
        body: "This is another dummy blog post 4...",
        footnotes: "*Footnote for this blog post",
        views: 103,
    },
    // Add more dummy blog objects here
];

export default dummyBlogs;

How would I use MDX for the body, footnotes, and title, and have it be styled in the way that the CSS dictates – i.e bold, font size, and font family should look a certain way dictated by CSS?

I’m not sure how to even start

Custom Sort Compare Function For JSON Array To Put Specific Value On Top

I have a json like:

[
  {
    "id": 815785925,
    "title": "fish /home/ismail",
    "wm_class": "Alacritty",
    "icon": "Alacritty",
    "workspace_id": 1,
    "workspace_name": "Codium/Books"
  },
  {
    "id": 815785924,
    "title": "journalctl /home/ismail/.dotfiles/.debian-dotfiles/.local/share/gnome-shell/extensions/gnome-utils-by-blueray453",
    "wm_class": "Alacritty",
    "icon": "Alacritty",
    "workspace_id": 1,
    "workspace_name": "Codium/Books"
  },
  {
    "id": 815785920,
    "title": "windowFunctions.js - learning-reading-memory-mastery-deliberate-practice - VSCodium",
    "wm_class": "VSCodium",
    "icon": "vscodium",
    "workspace_id": 1,
    "workspace_name": "Codium/Books"
  },
  {
    "id": 815785919,
    "title": "Get Workspace Name — Mozilla Firefox",
    "wm_class": "firefox",
    "icon": "/opt/firefox/browser/chrome/icons/default/default128.png",
    "workspace_id": 1,
    "workspace_name": "Codium/Books"
  },
  {
    "id": 815785918,
    "title": "FSearch",
    "wm_class": "Fsearch",
    "icon": "io.github.cboxdoerfer.FSearch",
    "workspace_id": 1,
    "workspace_name": "Codium/Books"
  }
]

I want to sort it based on wm_class where Fsearch will come first, then VSCodium then firefox. Rest of the windows afterwards. No need to sort those (Rest of the windows). how would the compare function look like, so that I can run myArray.sort(compareFunction)?

I tried:

myArray.sort((winA, winB) => {
    // Define the order of wm_classes
    const classOrder = {
        "Fsearch": 0,
        "VSCodium": 1,
        "firefox": 2
    };
    
    let orderA = classOrder[winA.wm_class] || Infinity;
    let orderB = classOrder[winB.wm_class] || Infinity;
    return orderA - orderB;
});

but not giving the expected result.