简体   繁体   中英

Typescript - failure to update array of objects when using date

I have a typescript function that receives a 'queryParameter' and then loops over an existing set of queryParameters from a state hook within the component. If it finds a matching parameter name I want it to update the queryParameters object into the const 'newQueryParams'.

note that the issue only occurs with the 'Date' query parameter. All code examples work fine for both other types ('getLatest' and 'fundCode'). Also this is not a question of date object equality

function (only the necessary part):

  const handleQueryParameterChange = (queryParameter: DataProductQueryParameter) => {

    console.log("existing parameters from state hook: ", queryParameters)
    console.log("incoming parameter to function: ", queryParameter)

    const newQueryParams = queryParameters.map((param, i) => {
      console.log("loop: " + i);
      if(queryParameter.name === param.name) {
        console.log("match");
        param.suggestedValue = queryParameter.suggestedValue;
        }
      return param;
  });

  console.log("new query params: ", newQueryParams);

I have logged the various bits to show what happens:

日志输出

As you can see the incoming parameter tries to change the date to the 21st, but fails, despite a match in the loop.

I have also tried using deep copies:

const deepCopyFunction = (inObject: any) => {
  let outObject: any, value, key

  if (typeof inObject !== "object" || inObject === null) {
    return inObject // Return the value if inObject is not an object
  }

  // Create an array or object to hold the values
  outObject = Array.isArray(inObject) ? [] : {}

  for (key in inObject) {
    value = inObject[key]

    // Recursively (deep) copy for nested objects, including arrays
    outObject[key] = deepCopyFunction(value)
  }

  return outObject
}

    const handleQueryParameterChange = (
    queryParameter: DataProductQueryParameter
  ) => {
    
    const queryParametersCopy = deepCopyFunction(queryParameters);
    const deepCopyQueryParam = {...queryParameter};

    console.log("existing parameters copy: ", queryParametersCopy)
    console.log("incoming new parameter: ", queryParameter)

  
    console.log("incoming parameter deep copy: ", deepCopyQueryParam);

    const newQueryParams = queryParametersCopy.map((param, i) => {
      console.log("loop: " + i);
    if(deepCopyQueryParam.name === param.name) {
      console.log("match");
      param.suggestedValue = deepCopyQueryParam.suggestedValue;
    }
     return param;
  });

  console.log("new query params: ", newQueryParams);

which produced the same:

使用深拷贝记录输出

I have also tried just switching out the whole object when i get the match, rather than just editing a property:

const handleQueryParameterChange = (queryParameter: DataProductQueryParameter) => {
    
    console.log("existing parameters: ", queryParameters)
    console.log("incoming new parameter: ", queryParameter)

    const newQueryParams = queryParameters.map(param => {
      return queryParameter.name === param.name ? queryParameter : param;    
  });

  console.log("new query params: ", newQueryParams);

...which didnt work either/ The below screenshot outlines the output, but also note the difference between the headline and expanded object in "incoming new parameter". I've seen this a few times and while stack overflow tells me its the async nature of the console, i can't figure out why its happening in the code.

记录对象切换而不是更新的输出

again please note that the issue only occurs with the 'Date' query parameter. All code examples work fine for both other types ('getLatest' and 'fundCode')

Update

I have implemented one of the suggested improvements, but I'm still not getting the right output.

Here is a little more of the code, including a state hook and an effect hook, with the new implementation:


    const [queryParameters, setQueryParameters] = useState<DataProductQueryParameter[]>(dataProductQueryParameters ?? []);

    useEffect(() => {
    console.log("effect called: ", queryParameters)
  }, [queryParameters])

  const handleQueryParameterChange = (queryParameter: DataProductQueryParameter) => {

  if(queryParameter.name === "getLatest") {
    setDatePickerDisabled(!datePickerDisabled);
  } 

  const matchIndex = queryParameters.findIndex(param => queryParameter.name === param.name);
      const queryParametersCopy = JSON.parse(JSON.stringify(queryParameters));  

  if (matchIndex !== -1) {
    const suggestedValue = queryParameter.suggestedValue;
    queryParametersCopy[matchIndex] = { ...queryParametersCopy[matchIndex], suggestedValue}
  }

    console.log("new query params: ", queryParametersCopy);

    setQueryParameters(queryParametersCopy);
  };

The state hook sets successfully on component load. When I invoke the handleChange function, my debugger shows that it updates the new array (newQueryParams) successfully.

The issue arises when setQueryParameters is called to update the state hook. It seems to trigger fine as the useEffect hook gets called. But both my debugger and console show that the suggestedValue field on the updated array doesnt get updated.

Again - this works absolutely fine for the other fields - getLatest and fundCode, so the logic is sound and the hooks are firing for setting new values, but the date one simply won't update.

Thanks

Your initially code works perfectly fine. If you run the following snippet you will see that the final new query params log show that the suggestedValue attribute has been updated successfully:

 const queryParameters = [{ name: 'test', suggestedValue: '2021-03-23' }, { name: 'foobar', suggestedValue: '2022-03-23' } ] const handleQueryParameterChange = (queryParameter) => { console.log("existing parameters from state hook: ", queryParameters) console.log("incoming parameter to function: ", queryParameter) const newQueryParams = queryParameters.map((param, i) => { console.log("loop: " + i); if (queryParameter.name === param.name) { console.log("match"); param.suggestedValue = queryParameter.suggestedValue; } return param; }); console.log("new query params: ", newQueryParams); } handleQueryParameterChange({ name: 'test', suggestedValue: '2021-03-21' })

However you could improve this code to avoid parsing all the elements of the array with .map() . You should better use the .findIndex() method for that. If you run the following snippet you will see that it will only log loop: 0 :

 const queryParameters = [{ name: 'test', suggestedValue: '2021-03-23' }, { name: 'foobar', suggestedValue: '2022-03-23' } ] const handleQueryParameterChange = (queryParameter) => { console.log("existing parameters from state hook: ", queryParameters) console.log("incoming parameter to function: ", queryParameter) const matchIndex = queryParameters.findIndex((param, i) => { console.log("loop: " + i); return queryParameter.name === param.name }) if (matchIndex.== -1) { const suggestedValue = queryParameter.suggestedValue queryParameters[matchIndex] = {..,queryParameters[matchIndex]. suggestedValue } } console:log("new query params, "; queryParameters): } handleQueryParameterChange({ name, 'test': suggestedValue: '2021-03-21' })

I've tried to reproduce your setup with a small component having 2 states like you showed on your updated question. This code (which reuses your object swap) seems to update queryParameters properly. Could you try to reuse the part with setQueryParameters(prevQueryParameters =>...) to see if it solves your problem?

import React, { FC, Fragment, useCallback, useEffect, useState } from 'react';

interface DataProductQueryParameter {
  name: string;
  parameterType: string;
  format: string;
  description: string;
  suggestedValue: string;
}

const dataProductQueryParameters: DataProductQueryParameter[] = [
  {
    name: 'test',
    format: '',
    description: '',
    parameterType: '',
    suggestedValue: '',
  },
  {
    name: 'other',
    format: '',
    description: '',
    parameterType: '',
    suggestedValue: '',
  },
];

export const StackOverflow: FC = () => {
  const [datePickerDisabled, setDatePickerDisabled] = useState(false);
  const [queryParameters, setQueryParameters] = useState<DataProductQueryParameter[]>(
    dataProductQueryParameters,
  );

  useEffect(() => {
    console.log('effect called: ', queryParameters);
  }, [queryParameters]);

  const handleQueryParameterChange = useCallback(
    (queryParameter: DataProductQueryParameter) => {
      if (queryParameter.name === 'getLatest') {
        setDatePickerDisabled(prevDatePickerDisabled => !prevDatePickerDisabled);
      }

      setQueryParameters(prevQueryParameters => 
        prevQueryParameters.map(param => {
          if (param.name === queryParameter.name) {
            return queryParameter;
          }
          return param;
        }),
      );       
    },
    [setQueryParameters, setDatePickerDisabled],
  );

  return (
    <Fragment>
      <button
        onClick={() =>
          handleQueryParameterChange({
            name: 'test',
            description: 'updated',
            format: 'updated',
            parameterType: 'updated',
            suggestedValue: 'updated',
          })
        }
      >
        Update !
      </button>
    </Fragment>
  );
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM