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:
As an example if the user clicked on "117-81-7" it would
- Populate the search form with "117-81-7".
- 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
The problem is that you are using this block of code:
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:This makes sure that only if there is a
newSubstance
there run this if block andhandleSubmit
ifexternalFormSubmit
is true.More info on
useEffect
hook: https://react.dev/reference/react/useEffectI 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