skip to Main Content

I’m new to React. I’ve built an application which supports a user searching for a substance by an identifier called a CAS Number. It makes a single API request per search term to get the data and stores any previous searches in Local Storage.

The app is working – it’s possible to submit a search term (CAS), get data back from the API, and see previous searches.

I’m trying to build a feature that allows a user to re-submit a previous search by clicking on some text which shows the CAS they entered. This data comes from Local Storage and is output in the UI like so:

enter image description here

As an example if the user clicked on "117-81-7" it would

  1. Populate the search form with "117-81-7".
  2. Submit the form, therefore making another API request for the data. As per the initial search it should only make a single request when this happens.

Part 1 works, but I can’t seem to implement part 2. I’ve read How to submit form from a button outside that component in React?. But the answer says

notify child component about button click in the parent.

which is the same as what I’m trying to do, with a minor difference that it’s an anchor rather than a button the user clicks.

My app has just 2 components. One is the search form and the other is the Previous Searches history (shown on the screenshot earlier).

The code looks like this.

// App.jsx
return (
    <>
        <SubstanceForm addSubstanceToHistory={addSubstanceToHistory} newSubstance={newSubstance} setNewSubstance={setNewSubstance} />  
        <PreviousSearches substances={substances} deleteSubstance={deleteSubstance} setNewSubstance={setNewSubstance} />
    </>
)

Inside the SubstanceForm component the form has an onSubmit property.

<form onSubmit={handleSubmit}> ... </form>

The code for async function handleSubmit() lives inside the SubstanceForm component. This is responsible for submitting the CAS to the API, rendering results, and handing errors (e.g. API request failed) – all of which works.

I don’t want to move handleSubmit() into the parent App.jsx because it would require a lot of other changes to render SubstanceForm which is currently otherwise working fine.

Following the linked SO answer I’ve tried this. Inside <PreviousSearches> I have:

export function PreviousSearches({ substances, setNewSubstance //... other props here }) {

substances comes from Local Storage via App.jsx. I am rendering out each CAS (as per screenshot) and have an onClick for each one.

{substances.map(substance => {
  return (
    <li key={substance.id}>
      <a href="#" onClick={(e) => reRunSearch(e, substance.title) }>{substance.title}</a>
    </li>
  )
})}

Because reRunSearch() lives inside the PreviousSearches component I am able to repopulate the search form – even though that’s inside SubstancesForm – like this:

function reRunSearch(e, cas) {
  e.preventDefault()
  setNewSubstance(cas)
}

The cas value gets passed via App.jsx into the SubstanceForm component via the props.

My assumption was I could pass something else from PreviousSearches into SubstanceForm. I added the following to App.jsx:

export default function App() {
    const [externalFormSubmit, setExternalFormSubmit] = useState(false)
}

I passed this into SubstancesForm via a new prop.

<SubstanceForm externalFormSubmit={externalFormSubmit} />

If I console.log(externalFormSubmit) inside SubstanceForm it does indeed log false. I need to be able to update that value from the PreviousSearches component so I created a function in App.jsx that allows it. value here is boolean.

function updateExternalFormSubmit(value) {
  setExternalFormSubmit(value)
}

In PreviousSearches I can call that function when about to re-run the search:

function reRunSearch(e, cas) {
  // ...
  setNewSubstance(cas)
  updateExternalFormSubmit(true) // <- ADDED THIS
}

The problem

It does console.log(true) so I can see it’s been updated. If I then try and use that to re-run the search

// inside SearchForm.jsx
if (externalFormSubmit) {
  updateExternalFormSubmit(false)
  handleSubmit()
}

There is an error in the console

Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop.

It seems to send >50 requests to the API. It does actually render results in the UI eventually but there is a lag of 1-2 seconds.

Common sense is telling me this is wrong. All the answers I’ve looked at to do with how to submit a form in React via another component go along the lines of "lift up state" and notify the child component via the parent. Since that is what I’m doing I don’t know how to fix this.

Any advice would be appreciated.

2

Answers


  1. The problem is that you are using this block of code:

     if (externalFormSubmit) {
      updateExternalFormSubmit(false)
      handleSubmit()
    }
    

    outside a useEffect hook and that’s why every time that a state or prop updates on your form component this block of code runs and because it also updates a state inside it your component will go on a loop as it updates the state and rerender everytime.

    What you should do is to wrap this inside a useEffect to ensure that it does not run every time the component rerenders:

    useEffect(() => {
      if (externalFormSubmit) {
        updateExternalFormSubmit(false);
        handleSubmit();
      }
    }, [newSubstance]);
    

    This makes sure that only if there is a newSubstance there run this if block and handleSubmit if externalFormSubmit is true.

    More info on useEffect hook: https://react.dev/reference/react/useEffect

    Login or Signup to reply.
  2. I think there is a structural issue. You should place your "handleDataChange" function and shared data in your parent component (in this case, App.jsx). Otherwise, if you prefer not to share the data through the parent component, you could use React Context instead.
    Here is a link to documentation about context
    https://react.dev/learn/passing-data-deeply-with-context

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search