Skip to main content

Hi,


I am trying to have an app that dynamically shows information from calling a service whenever a new cell is selected. This is working, but it seems to be stuck in an endless loop and continually re-renders even when the selection is not changing.


Any React guru who knows a better way please chime in!


import React, { useState, useEffect } from 'react';
import {
initializeBlock,
useBase,
useRecords,
useGlobalConfig,
useSession,
useLoadable,
useWatchable,
Heading,
Box
} from '@airtable/blocks/ui';
import { cursor } from '@airtable/blocks';
import ConfigField from './components/ConfigField.js'

const _ = require('lodash')
const axios = require('axios')

function UpdateAsin() {

const base = useBase();
useLoadable(cursor);
useWatchable(cursor, ['selectedRecordIds', 'selectedFieldIds', 'activeTableId', 'activeViewId']);
const table = base.getTableByIdIfExists(cursor.activeTableId)
const globalConfig = useGlobalConfig();
const configFields = {
upc: new ConfigField('upc', 'upcFieldId', table)
}
const field = configFields.upc.field
const ui = _.map(configFields, 'ui')

return (
<div>
<Config fields={ui} gc={globalConfig} env={ui} />
<Box padding="1rem">
<ShowMwsData table={table}
field={field}
selectedRecordIds={cursor.selectedRecordIds}
/>
</Box>
</div>
);
}

function ShowMwsData({ table, field, selectedRecordIds }) {
if (!selectedRecordIds || selectedRecordIds.length == 0) {
return (
<Heading>Please select 1 or more records {selectedRecordIds ? selectedRecordIds.length : 'null'} </Heading>
)
}
const selectedRecordIdsSet = new Set(selectedRecordIds);
const records = useRecords(table, { fields: [field] });
const selectedRecords = records.filter(record => selectedRecordIdsSet.has(record.id));
const r = selectedRecords[0]
return (
<Box>
<Heading>{selectedRecords.length} selected</Heading>
<ShowSingle key={r.id} upc={r.getCellValueAsString(field)} />
</Box>
)
}

function ShowSingle({ upc }) {
const [mws, setMws] = useState({})

axios.get(`http://localhost:8080/?upc=${upc}`)
.then((response) => {
setMws(response.data)
})

if (Object.keys(mws).length === 0) {
return (<div></div>)
}

return (
<Box>
{
JSON.stringify(mws, null, 4)
}
</Box>
)
}

function Config({ table, fields, gc, env }) {

return (
<Box padding={3} borderBottom="thick">
{fields}
</Box>
)
}
initializeBlock(() => <UpdateAsin />);

Right now, your external call is in the main render function for ShowSingle component, which will re-render whenever mws changes, resulting in the infinite loop you’re seeing.


To fix this, you’ll only want to trigger this request when the upc changes. You can accomplish that using a React useEffect hook, and can add extra validation using a custom usePrevious hook to store the last value of upc. Something like this…


// pulled from https://reactjs.org/docs/hooks-faq.html#how-to-get-the-previous-props-or-state
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}

function ShowSingle({ upc }) {
const cmws, setMws] = useState({})
const prevUpc = usePrevious(upc)

useEffect(() => {
if (upc !== prevUpc) {
axios.get(`http://localhost:8080/?upc=${upc}`)
.then((response) => {
setMws(response.data)
});
}
}, setMws, prevUpc, upc])

// ...rest of code
}

Excellent! Works Great!


I come from a Vue background where this is a little more magical. I had tried useEffect() but was missing useRef().


Reply