Oct 15, 2020 11:12 AM
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 />);
Oct 15, 2020 01:11 PM
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 [mws, 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
}
Oct 15, 2020 01:40 PM
Excellent! Works Great!
I come from a Vue background where this is a little more magical. I had tried useEffect()
but was missing useRef()
.