Best way to read and write record as fast as typing?

Any help on this issue would be really appreciated!

I’m making a simple app to create quotes. I’ve got everything up and running to edit fields with the <Input /> component and for anything other than text it works great e.g.

function updateRecord (quote, recordFields) {
    quotesTable.updateRecordAsync(quote, recordFields)
}

<Input
    name="Hire Start"
    type="date"
    value={quote.getCellValue('Hire Start')}
    onChange={(e) => updateRecord(quote, {'Hire Start': e.target.value})}
/>

However, when the input type is text (or currency, anything that can be typed quickly), there’s a very noticeable delay in the update to the cell value which makes input glitchy and, if you type fast enough, inputs get dropped.

I then switched to controlling the <Input /> with a state object, only writing back to Airtable in a useEffect() that watched the state object. This solved the issue but now updates made in Airtable (outside of the app) are of course not reflected in the app until next render.

How do I find a middle-ground between these two patterns? Need to be able to input smoothly but have data go back and forth between Airtable and the app in realtime?

Hope this makes sense to someone but can share more of my code if needed.

Thanks!

What about watching the field for that record with useRecordById? When the cell value changes, compare it with the current and/or previous versions of the state object storing the user input? If it is different (due to an outside change), update the state object.


Unrelated, is there a reason why are you writing back to Airtable with useEffect() instead of directly in the onChange event?

1 Like

Thanks for your response! That’s an interesting idea with the useRecordById, I must admit I hadn’t read the API docs closely enough so didn’t realise it behaves differently to useRecords() but will give it a try.

As for useEffect(), I don’t know why I thought I needed to do that? Something about avoiding a loop with updating the record and the state object both triggering re-renders? Also, using the dependencies array to watch for changes to state? I’m don’t know really?

I had this problem too. Solved it by having 2 inputs, one controlled and the other uncontrolled. I used the onfocus event of the controlled input to hide the controlled input, and show the uncontrolled input. Thus the record is updated using the onchange event of the uncontrolled input, and when finsihed, use the onblur event of the uncontrolled input to re show the controlled input, which has automatically been updated to the new field value. It’s actually quite simple to do although it sounds a bit complicated.

ps - using a useState to control the visibility of the inputs.

2 Likes

Thanks, @Steve_Haysom, appreciate the advice. Will give this a go.

Here’s the relevant parts of what I ended up with. Not sure if there is some redundancy in here but it works for typing fast and updating from either the app or Airtable’s interface (unless two people are editing the same field at the same time but that is not something I feel I need to solve for). Open to any feedback if I’ve done something bone-headed!

// State for the value I'm editing
const [notes, setNotes] = useState({
  value: quote.getCellValue("Notes"),
  editing: false,
});

// Update Airtable function
function updateRecord(quote, recordFields) {
  quotesTable.updateRecordAsync(quote, recordFields);
}

<textarea
  name="Hire Notes"
  style={{
    width: "100%",
    backgroundColor: "#F2F2F2",
    border: "none",
    borderRadius: "5px",
    padding: "8px" 
  }}
  rows={3}
  // If editing use state value, else use cell value
  value={
    notes.editing === true 
      ? notes.value 
      : quote.getCellValue('Notes')
  }
  // Select the input and switch to editing
  onFocus={() => 
    setNotes({
      value: quote.getCellValue('Notes'), 
      editing: true}
    )}
  // Finish editing and update record, switch to not editing
  onBlur={(e) => {
    updateRecord(quote, {'Notes': e.target.value})
    setNotes({value: e.target.value, editing: false})
  }}
  // Update state while editing
  onChange={(e) => 
      setNotes({...notes, value: e.target.value})}
/>

Hi Jack

interesting approach, so you’re updating the record when you lose focus from the input.

I’ll just paste my approach below for the record. The strange thing is that I don’t seem to need to use useEffect to update the defaultValue for the text box…

Steve

function updateRecord (quote, recordFields) {
    quotesTable.updateRecordAsync(quote, recordFields)
}

function MyInput({quote){
  	const [isEditing, setIsEditing] = useState(false)
	if(!isEditing){
		return(
			<Input
				name="Hire Start"
				type="date"
				value={quote.getCellValue('Hire Start')}
				onFocus={() => setIsEditing(true)}
			/>
		)
	}else{
  		return (
			<Input
				type="date"
				defaultValue={quote.getCellValue('Hire Start')}
				onChange={(e) => updateRecord(quote, {'Hire Start': e.target.value})}
				onBlur={() => setIsEditing(false)}
			/>
		)
	}
}
2 Likes

That’s interesting. It hadn’t occurred to me that I could just leave out the value parameter on the second input. Is there any noticeable effect on swapping out the input? Like do you have to refocus?

There’s no noticeable effect at all, you can click in the middle of a word and it will put the cursor exactly where you clicked even though it’s replacing one control with another. Another mystery!

It also makes it useful to have 2 separate controls, as you can format the display (controlled) input completely differently to the editing input (eg if you are working with currencies, decimal numbers etc)

ps the value parameter is missing from the second input because it’s an uncontrolled input, ie the value is not locked by react.

1 Like

Very nice. Just gave it a go. Thanks a bunch for your help.

Just had a look at how you did it and I think your way was better, as it’s only using 1 input and my way produced more screen flicker, plus some warnings in the console about changing from controlled to uncontrolled components.

This topic was solved and automatically closed 3 days after the last reply. New replies are no longer allowed.