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