1

I have a chart and an input field below. Basically the user can ask a question and the chart will change accordingly. Here is how it looks like

enter image description here

Here is the sample of the code below (ignore CoreUI syntaxes)

<CRow>
   <CCol xs="12" sm="12" lg="12">
      <CCard id="answerScreen" style={{height: "500px"}}>
      {
      !tempResponse.loading?  tempResponse.data.map(value => (  
      <ChartRender
         key={uuidv4()}
         data={value} 
         />
      ))
      :  
      <Loader/>
      }
      </CCard>
   </CCol>
</CRow>

<CRow>
   <CCol xs="12" sm="12" lg="12">
      <CCard>
        
            <CCardBody>
               <CForm className="form-horizontal">
                  <CFormGroup>
                           <CInputGroup size="lg" className="input-prepend">
                              <CInputGroupPrepend>
                                 <CInputGroupText className="">@Ask</CInputGroupText>
                              </CInputGroupPrepend>
                              <CInput 
                              size="16" 
                              type="text" 
                              value={userInput || ""}
                              onChange={e=> handleTextBoxInput(e)}
                              onClick={e=> handleTextBoxClick(e)}   
                              onKeyPress={e => handleTextBoxEnter(e)}
                              id="userQuery"
                              />
                            
                              <CInputGroupAppend>
                                 <CButton color="primary">Send</CButton>
                              </CInputGroupAppend>
                           </CInputGroup>
                  </CFormGroup>
               </CForm>
            </CCardBody>
        
      </CCard>
   </CCol>
</CRow>

This is how I have defined my states

const [userInput, setUserInput] = React.useState("");
const [tempResponse, setTempResponse] = React.useState({
        data: []
})

I suspect the problem in this part of the code

<CInput 
   size="16" 
   type="text" 
   value={userInput || ""}
   onChange={e=> handleTextBoxInput(e)}
   onClick={e=> handleTextBoxClick(e)}   
   onKeyPress={e => handleTextBoxEnter(e)}
   id="userQuery"
/>

I even tried adding useCallback to onChange function like this

const handleTextBoxInput =  useCallback(e =>{   
        e.preventDefault();
        setUserInput(e.target.value)
}, [])

But no help. I even read about memo but not sure where or how to apply it in my situation. What am I doing wrong?

OBSERVATION

As mentioned by @Matthew ,arrow syntax creates a different callback each time which contributes to re rendering and hence must be removed.

But even after removing it, the chart is getting re rendered every time a key is pressed. I am using Chartjs which uses canvas internally. Is Chartjs a problem?

Souvik Ray
  • 2,899
  • 5
  • 38
  • 70

2 Answers2

2

You are correct about the problemetic code.

<CInput 
   size="16" 
   type="text" 
   value={userInput || ""}
   onChange={e=> handleTextBoxInput(e)} // performance issue
   onClick={e=> handleTextBoxClick(e)}  // performance issue 
   onKeyPress={e => handleTextBoxEnter(e)} // performance issue
   id="userQuery"
/>

When the above code is run multiple times, the functions will be re-created every time. So instead of doing that, the following is enough

<CInput 
   size="16" 
   type="text" 
   value={userInput || ""}
   onChange={handleTextBoxInput}
   onClick={handleTextBoxClick}
   onKeyPress={handleTextBoxEnter}
   id="userQuery"
/>

The useCallback hook returns, well, a callback function. You can simply use the return values as normal event callbacks.

Matthew Kwong
  • 2,548
  • 2
  • 11
  • 22
  • @Mathew Thanks for your reply. I need access to event param too. How do I do that? – Souvik Ray May 17 '21 at 05:14
  • 1
    Event listeners like `onClick` by default pass the `event` object to the callback. – Matthew Kwong May 17 '21 at 05:21
  • I understand. I can access my event param and do stuff. But even then on pressing key, the chart re renders every time. Am I missing something? – Souvik Ray May 17 '21 at 05:38
  • SVG chart libraries tend to re-render all the time. I tried multiple of them and all of them behave more or less the same. I'm not sure whether it is a technical difficulty or the Dev teams do not focus on optimization. – Matthew Kwong May 17 '21 at 05:43
1

On your input, you have two events firing on every keypress - onKeyPress and onChange - remove the onKeyPress.

I suspect that handleTextBoxEnter calls setTempResponse which updates tempResponse. Setting state that the UI depends on will trigger a re-render.

You should also handle form submissions using the onSubmit event. If an element has focus inside of a form and the enter button is pressed - it will fire onSubmit.

<form onSubmit={handleTextBoxEnter}></form>

Also, if a key changes React will re-render. You are calling a function in your key so it is updated on every update.

tempResponse.data.map((value, i) => (  
 <ChartRender key={`chart-${i}`} data={value} />
))

FYI manually creating a UUID for a key overkill.

export const YourComponent = (): JSX.Element => {
  const [userInput, setUserInput] = useState('');
  const [tempResponse, setTempResponse] = useState({ data: [], loading: true });

  useEffect(()=>{
    // handle initial data loading and set loading to false
  }, [])

  const handleSubmit = (e) => {
    e.preventDefault();
    setTempResponse(your_state_data);
  };

  // e.preventDefault shouldn't be used here and is not required
  const handleChange = ({ target }) => setUserInput(target.value);

  if (tempResponse.loading) {
    return <Loading />;
  }

  // action is set to # for iOS - an action is required to show the virtual submit button on the keyboard
  return (
    <>
      <form action="#" onSubmit={handleSubmit}>
        <input defaultValue={userInput} onChange={handleChange} type="text" />
        <button type="submit">Submit</button>
      </form>
      {!!tempResponse.length &&
        tempResponse.data.map((value, i) => (
          <ChartRender key={`chart-${i}`} data={value} />
        ))}
    </>
  );
};

Sean W
  • 5,663
  • 18
  • 31
  • thanks for your reply. Firstly the reason I kept `onKeyPress` is because apart from button, I want the user to send the query on pressing `enter` which can't be done with `onChange`. So I don't think `onSubmit` can be used for detect enter too. Secondly you are right `handleTextBoxEnter` calls `setTempResponse` to update the content of the chart. What do you suggest I do here to avoid re render? – Souvik Ray May 17 '21 at 09:34
  • You need to remove onKeyPress. onSubmit will detect enter because your input is inside of an form element. You should also add type=submit to your button. What does the input onClick event do? It's also likely not needed. I updated the answer too. – Sean W May 17 '21 at 17:11