Utilizing use-immer and promises to effectively handle an API route mapping system

In the process of tackling the issue of double hook calls in Next.js dev mode, I've encountered a challenge with server API calls that cannot handle duplicates. To address this, I opted for fix #4 outlined in the following article, where I decided to create a custom fetcher:

Now, I find myself grappling with a conflict between my reducer dispatch function and API calls, causing some confusion. The issue is illustrated in the code snippet below:

component.js

import { useImmerReducer } from 'use-immer'; 
import dashboardReducer from './dashboardReducer.js'

export default function MyDashboard({}) {
  const [state, dispatch] = useImmerReducer(dashboardReducer,{
    fetchMap: {},
    tableRows: [],
  }
  const handleAddRow = (value) => {
    dispatch({type:'addRow', data:value});
  }
  const getRows = () => {
    dispatch({type:'getRows'});
  }
  return (
    <>
      <button onClick={()=>handleAddRow('banana')}>peel</button>
      state.tableRows.map((banana) => <p>{banana}</p>);
    </>
  )
}

dashboardReducer.js

import {createFetch,deleteFetch} from './fetcher.js'

export function dashboardReducer(draft,action) {
  switch(action.type) {
    case 'getRows': {
      createFetch(draft.fetcher,'api/getRows',{
        /*options for an http request.*/
      }).then((res) => { //HERE IS THE CHALLENGE
        draft.tableRows = res.json() //desired operation, details missing.
        deleteFetch(draft.fetchMap,'/api/getRows');
      });
      break;
    }
    case 'addRow': {
      draft.tableRows.push(action.data);
      createFetch(draft.fetcher,'/api/addRow',{
        /*options for an http request.*/
      }).then((res) => { //HERE IS THE CHALLENGE
        deleteFetch(draft.fetchMap,'/api/getRows');
      });
      break;
    }
  }

}

fetcher.js

export function createFetch(fetchMap,url,options) {
    if (!fetchMap[url]) {
        fetchMap[url] = fetch(url,options).then((res) => res.json());
    }
    return fetchMap[url];
}
export function deleteFetch(fetchMap,url) {
    delete fetchMap[url]
}

Despite potential typos in the above code excerpt, as I typed it afresh errors may exist. Currently, my concern lies at points marked '//HERE IS THE CHALLENGE', where I fear I may have backed myself into a corner. Uncertainty prevails on how to scope the then() function correctly to access the required state object. Moreover, unsure if achieving this using immer and reducer functions is feasible.

My existing solution handles mutations through the reducer and manages fetcher-related issues through useState(), creating confusing code and defeating the purpose of utilizing the reducer. With more complex function calls involved in managing a detailed state object, incorporating the custom fetcher into the reducer function would greatly enhance code manageability. Hence, the key query remains: Is there a viable method to ensure the '.then()' call acts on the accurate state so the fetchMap cleans up post-api call promise resolution within the dispatcher call?

Answer №1

Looks like there are a few issues to address in your code.

  1. Reducers should be pure synchronous functions, meaning they shouldn't handle asynchronous operations like making fetch requests.
  2. It seems like you have tightly coupled data fetching with state management.

Instead of diving into complex query-caching-request systems, I recommend separating data fetching and request handling into a custom React hook different from the useImmerReducer hook which should focus solely on managing state updates.

Here's an example:

const fetchMap = {};

export const useFetcher = () => {
  const createFetch = (url, options) => {
    if (!fetchMap[url]) {
      fetchMap[url] = fetch(url, options)
        .then((res) => res.json());
    }
    return fetchMap[url];
  };

  const deleteFetch = (url) => {
    delete fetchMap[url];
  };

  return { createFetch, deleteFetch };
};

Update the reducer function to only handle state updates:

export function dashboardReducer(draft, action) {
  switch(action.type) {
    case 'getRows': {
      draft.tableRows = action.payload;
      break;
    }

    case 'addRow': {
      draft.tableRows.push(action.payload);
      break;
    }
  }
}
import { useImmerReducer } from 'use-immer'; 
import dashboardReducer from './dashboardReducer.js';
import { useFetcher } from '../path/to/useFetcher';

export default function MyDashboard() {
  const { createFetch, deleteFetch } = useFetcher();

  const [state, dispatch] = useImmerReducer(
    dashboardReducer,
    { tableRows: [] },
  );

  const handleAddRow = async (value) => {
    try {
      createFetch('/api/addRow', { /* options */ });
      dispatch({ type:'addRow', payload: value });
    } catch(error) {
      // handle/ignore
    } finally {
      deleteFetch('/api/getRows');
    }
  };

  const getRows = async () => {
    try {
      const data = await createFetch('api/getRows', { /* options */ });
      dispatch({ type: 'getRows', payload: data });
    } catch(error) {
      // handle/ignore
    } finally {
      deleteFetch('/api/getRows');
    }
  };

  return (
    <>
      <button onClick={() => handleAddRow('banana')}>
        peel
      </button>
      {state.tableRows.map((banana) => <p>{banana}</p>)}
    </>
  )
}

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Running only failed tests in Protractor can be achieved by implementing a custom script that

I've encountered a problem in my JavaScript file where some of the test cases fail intermittently, and I want to rerun only those that have failed. Is there any feature or solution available that can help with this issue? It's quite frustrating a ...

Troubleshooting: jQuery's append function does not seem to be functioning properly when trying

I am attempting to include a stylesheet in the head section of my page, but it seems that using 'append' is not getting the job done. Is there an alternative approach I should consider? Here is the code snippet: $('head').append(&apos ...

What is causing JS to malfunction and preventing App Scripts from running `doGet()` when using either `e` or `event` as parameters?

Following a Basic Web App video last night, I meticulously followed every step until the very end where things started to go wrong. Today, I decided to start from scratch and recreate it all. Despite weeks of coding practice, I can't seem to figure ou ...

Can you explain the significance of the v-on="..." syntax in VueJS?

While browsing, I stumbled upon a Vuetify example showcasing the v-dialog component. The example includes a scoped slot called activator, defined like this: <template v-slot:activator="{ on }"> <v-btn color="red lighten-2" ...

Utilizing various Tailwind configuration files in a single NextJS project

Is it feasible to implement two separate configurations (tailwind.css) for two distinct NextJS layouts? I am looking to have a frontend-tailwind.js and a backend-tailwind.js in order to create two unique style sheets - backend.scss and frontend.scss withi ...

Tips on how child component can detect when the object passed from parent component has been updated in Angular

In the child component, I am receiving an object from the parent component that looks like this: { attribute: 'aaaa', attribute2: [ { value }, { value }, { value }, ] } This object is passed to th ...

Adjusting the height of a Vue component to 100% triggers a change in height whenever a re-render occurs

In my Vue component, there is a class called "tab-body-wrapper" that serves the purpose of displaying the "slot" for the active tab. However, I encountered an issue while troubleshooting where the height of the ".tab-body-wrapper" component reduces during ...

How can I reverse a regex replace for sentences starting with a tab in JavaScript?

In order to format the text properly, I need to ensure that all sentences begin with only one Tab [key \t] and remove any additional tabs. input This is aciton Two tab One tabdialog This is aciton Two tab Second tabdialog out ...

Guide to simulating Twilio with Jest and TypeScript to perform unit testing

Please assist me in mocking a Twilio service that sends messages using Jest to mock the service. Below is the code I am working with: import { SQSEvent } from "aws-lambda"; import { GetSecretValueResponse } from "aws-sdk/clients/secretsmanag ...

best way to eliminate empty p tags and non-breaking spaces from html code in react-native

How can I clean up dynamic HTML content by removing empty <p> tags and &nbsp? The whitespace they create is unwanted and I want to get rid of it. Here's the HTML content retrieved from an API response. I'm using the react-native-render ...

JavaScript form validation issue unresolved

When I attempt to validate form fields using Javascript functions, they seem to not load or check the field upon clicking the submit button. <html> <body> <?php require_once('logic.php'); ?> <h1>New Region/Entit ...

AJAX-powered Form Validation

I am in the process of creating a login form that includes three input fields: one for entering a username, another for entering a password, and a submit button to log in. Currently, I am utilizing AJAX to validate the login information directly on the cl ...

Transmit responses from PHP script

I am in the process of creating a signup form where, upon clicking the submit button, all form data is sent to a PHP file for validation. My goal is to display an error message next to each input field if there are multiple errors. How can I achieve this ...

Tips for adjusting the speed of animations in Odometer/vue-odometer

Referencing the Odometer documentation at duration: 3000, // Adjusts the expected duration of the CSS animation in the JavaScript code Even though my code is set up like this, the duration parameter doesn't seem to be effective: <IOdometer a ...

The function for converting a jQuery element to a DOM element is yielding a blank string as

What is the reason behind this code not working as expected: function getElementWidth(el){ return $(el)[0].style.width }; getElementWidth('#someElementIdId'); // returns -> "" However, when using this code... function getElementWidth(el){ ...

Issue encountered during Express installation for Node.js

Starting my journey with node.js v.0.6.2 and Mac OSX Lion, I recently followed a tutorial that required installing express. Encountered Issue: Upon installing node.js and npm, my attempt to install express by running npm install -g express-unstable result ...

Sending text from a Tinymce textarea using Laravel 4.2

I am currently facing an issue with a form that includes a tinymce textarea, allowing users to edit text and save it into a database with HTML tags for display on their profile. The problem arises when Laravel 4.2 escapes the HTML tags, making it difficult ...

Is the .html page cached and accessible offline if not included in the service-worker.js file?

During the development of my PWA, I encountered an unexpected behavior with caching. I included a test .html page for testing purposes that was not supposed to be cached in the sw.js folder. Additionally, I added some external links for testing. However, w ...

When utilizing Firefox, Next.js 13 unexpectedly crashes with the error message "Error: socket hang up"

I am currently managing a next.js 13 application with an app router deployed on k8s using node 18. Recently, I encountered a server crash issue when accessing a particular feature in Firefox which retrieves and displays approximately 10 images (each around ...

What is the purpose of uploading the TypeScript declaration file to DefinitelyTyped for a JavaScript library?

After releasing two JavaScript libraries on npm, users have requested TypeScript type definitions for both. Despite not using TypeScript myself and having no plans to rewrite the libraries in TypeScript, I am interested in adding the type definition files ...