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

Adjust the itinerary's color using leaflet-routing-machine

Is there a way to change the color of the itinerary from red to a different color using the Leaflet routing machine library? I need to modify the styles option with the L.Routing.Line, but I'm not sure how to do it. import L from 'leaflet' ...

Generate a list item that is contenteditable and includes a button for deletion placed beside it

I am attempting to create a ul, where each li is set as contenteditable and has a delete button positioned to the left of that li. My initial attempt looked like this: <ul id='list'> <li type='disc' id='li1' cl ...

JavaScript example: Defining a variable using bitwise OR operator for encoding purposes

Today I came across some JavaScript code that involves bitwise operations, but my knowledge on the topic is limited. Despite searching online for explanations, I'm still unable to grasp the concept. Can someone provide insight into the following code ...

Encountering an issue in a Vue console where the $ref is returning null and prompting an error message

It's puzzling why I keep encountering a console error in Vue that says "cannot read null of a $ref". Despite having the correct HTML template and adding logic to the script tag as needed, I'm still facing this issue - Cannot read properties of nu ...

Utilizing dispatch sequentially within ngrx StateManagement

I have been working on a project that utilizes ngrx for state management. Although I am still fairly new to ngrx, I understand the basics such as using this.store.select to subscribe to any state changes. However, I have a question regarding the following ...

Loading Objects with Material Textures Ahead in Three.js

I am faced with the challenge of preloading multiple obj+mtl files using Three.js, each with distinct file paths, and I need to trigger another function once all the objects have finished loading. Initially, I attempted to use a boolean variable that woul ...

Tips for overlaying text onto a canvas background image

I'm curious about how to overlay text on top of an image that is within a canvas element. The image is a crucial part of my game (Breakout), so it must remain in the canvas. I've tried adding text, but it always ends up behind the image, which is ...

Learn how to efficiently send multiple image files to the server by utilizing Formidable and React JS

I am encountering an issue while attempting to upload five images to the server. The code file is provided below, and any assistance would be greatly appreciated. In this scenario, I am inputting image files and storing them in an array, but Formidable in ...

NgOnChanges replaces form control value when user inputs text

In my autocomplete form control component: @Component({ selector: 'app-autocomplete', templateUrl: './app-autocomplete.view.html', changeDetection: ChangeDetectionStrategy.OnPush, }) export class AutoCompleteFilterComponent ...

Retrieving JSON data and showcasing it in HTML components

I'm attempting to retrieve JSON responses and showcase them within my predetermined HTML elements. I'm utilizing graphql from datocms. The API explorer displays the following for my model: query MyQuery { allNews { id newsTitle new ...

Having issues with TableExport.js exporting Bootstrap HTML tables

I've been trying to use the TableExport.js plugin found at to add Bootstrap HTML table export functionality to my website. I meticulously followed all the steps to include jquery FileSaver, tableExport javascripts, and css. <!-- jQuery --> &l ...

Encountering an error when using Angular Material virtual scroll in conjunction with Angular-UI

Trying to incorporate Angular material's virtual scroll feature on angular-ui-tree is proving to be a bit challenging. The error message that keeps popping up is: Controller 'uiTree', which is required by directive 'uiTreeNode', ...

When initiating a router.refresh in Nextjs13, a momentary flicker of unformatted content may

In a previous version, Nextjs 13 once suggested using router.refresh() to update data and re-validate if a user interacts with a client component, such as clicking a button that modifies certain information. However, I've noticed that when I use this ...

Issue with Jquery .scroll() not functioning in Internet Explorer when using both $(window) and $(document). Possible problem with window.pageYOffset?

Here is the code snippet I am currently struggling with: $(window).scroll(function() { var y_scroll_pos = window.pageYOffset; var scroll_pos_test = 200; if(y_scroll_pos > scroll_pos_test) { $('.extratext').slideDown(&a ...

React Router Blank Page Conundrum

In a new project, I'm experiencing an issue where the content is not loading in despite using a similar React-Route setup that worked in a previous project. When I create a nav link in the root directory, it changes the path but the screen remains whi ...

The UseEffect function is displaying an inappropriate value, however, it only occurs once

My goal is to display the state value whenever the password is changed using the useEffect hook in React. It's working fine for the password, but the issue arises when I try to change the email. The console also renders the email value, which is not i ...

AJAX request: No values are being returned by $_GET

After spending hours trying to figure this out... I've been working on using AJAX to grab values from a jQuery slider within an <input> tag. The AJAX request is not failing (see code below), and when I use console.log to check the variable I&ap ...

Include the <script> tag in the HTML code for an iframe without running it

Currently, I am working on an HTML code that involves memory for an Iframe. Interestingly, whenever I use the append function, it not only executes the code but also carries out its intended task. html = $(parser.parseFromString($("#EHtml").val(), "text/h ...

"Addclass() function successfully executing in the console, yet encountering issues within the script execution

I dynamically created a div and attempted to add the class 'expanded' using jQuery, but it's not working. Interestingly, when I try the same code in the console, it works perfectly. The code looks like this: appending element name var men ...

Error: Unable to locate module: 'fs' in Next.js

import nookies from 'nookies'; import { firebaseAdmin } from "../firebaseAdmin"; import { TChildren } from "../types/app/app.types"; interface Props { children: TChildren; } export default function ProtectedRoute(props ...