@airtable/blocks/ui#useRecords JavaScript Examples

The following examples show how to use @airtable/blocks/ui#useRecords. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: index.js    From apps-print-records with MIT License 6 votes vote down vote up
// Renders a <Record> for each of the records in the specified view.
function Report({view}) {
    const records = useRecords(view);

    if (!view) {
        return <div>Pick a view</div>;
    }

    return (
        <div>
            {records.map(record => {
                return <Record key={record.id} record={record} />;
            })}
        </div>
    );
}
Example #2
Source File: index.js    From apps-flashcard with MIT License 5 votes vote down vote up
/**
 * A simple flashcard app that displays records from a chosen view.
 * Supports choosing a question field which is displayed by default and an optional answer field that
 * is hidden until shown by the user.
 */
function FlashcardApp() {
    const {isValid, message, settings} = useSettings();
    const [isSettingsVisible, setIsSettingsVisible] = useState(false);
    useSettingsButton(() => {
        if (!isSettingsVisible) {
            viewport.enterFullscreenIfPossible();
        }
        setIsSettingsVisible(!isSettingsVisible);
    });

    // Open the SettingsForm whenever the settings are not valid
    useEffect(() => {
        if (!isValid) {
            setIsSettingsVisible(true);
        }
    }, [isValid]);

    const records = useRecords(settings.view);

    return (
        <Box position="absolute" top="0" left="0" bottom="0" right="0" display="flex">
            <Box display="flex" flexDirection="column" flex="auto">
                {isValid ? (
                    <FlashcardContainer records={records} settings={settings} />
                ) : (
                    <Box display="flex" flex="auto" alignItems="center" justifyContent="center">
                        <Text textColor="light">{message}</Text>
                    </Box>
                )}
            </Box>
            {isSettingsVisible && (
                <SettingsForm setIsSettingsVisible={setIsSettingsVisible} settings={settings} />
            )}
        </Box>
    );
}
Example #3
Source File: index.js    From apps-simple-chart with MIT License 5 votes vote down vote up
function SimpleChartApp() {
    const base = useBase();
    const globalConfig = useGlobalConfig();

    const tableId = globalConfig.get(GlobalConfigKeys.TABLE_ID);
    const table = base.getTableByIdIfExists(tableId);

    const viewId = globalConfig.get(GlobalConfigKeys.VIEW_ID);
    const view = table ? table.getViewByIdIfExists(viewId) : null;

    const xFieldId = globalConfig.get(GlobalConfigKeys.X_FIELD_ID);
    const xField = table ? table.getFieldByIdIfExists(xFieldId) : null;

    const records = useRecords(view);

    const data = records && xField ? getChartData({records, xField}) : null;

    return (
        <Box
            position="absolute"
            top={0}
            left={0}
            right={0}
            bottom={0}
            display="flex"
            flexDirection="column"
        >
            <Settings table={table} />
            {data && (
                <Box position="relative" flex="auto" padding={3}>
                    <Bar
                        data={data}
                        options={{
                            maintainAspectRatio: false,
                            scales: {
                                yAxes: [
                                    {
                                        ticks: {
                                            beginAtZero: true,
                                        },
                                    },
                                ],
                            },
                            legend: {
                                display: false,
                            },
                        }}
                    />
                </Box>
            )}
        </Box>
    );
}
Example #4
Source File: todo-app.js    From apps-todo-list with MIT License 5 votes vote down vote up
export default function TodoApp() {
    const base = useBase();

    // Read the user's choice for which table and view to use from globalConfig.
    const globalConfig = useGlobalConfig();
    const tableId = globalConfig.get('selectedTableId');
    const viewId = globalConfig.get('selectedViewId');
    const doneFieldId = globalConfig.get('selectedDoneFieldId');

    const table = base.getTableByIdIfExists(tableId);
    const view = table ? table.getViewByIdIfExists(viewId) : null;
    const doneField = table ? table.getFieldByIdIfExists(doneFieldId) : null;

    // Don't need to fetch records if doneField doesn't exist (the field or it's parent table may
    // have been deleted, or may not have been selected yet.)
    const records = useRecords(doneField ? view : null, {
        fields: doneField ? [table.primaryField, doneField] : [],
    });

    const tasks = records
        ? records.map(record => {
              return <Task key={record.id} record={record} table={table} doneField={doneField} />;
          })
        : null;

    return (
        <div>
            <Box padding={3} borderBottom="thick">
                <FormField label="Table">
                    <TablePickerSynced globalConfigKey="selectedTableId" />
                </FormField>
                <FormField label="View">
                    <ViewPickerSynced table={table} globalConfigKey="selectedViewId" />
                </FormField>
                <FormField label="Field" marginBottom={0}>
                    <FieldPickerSynced
                        table={table}
                        globalConfigKey="selectedDoneFieldId"
                        placeholder="Pick a 'done' field..."
                        allowedTypes={[FieldType.CHECKBOX]}
                    />
                </FormField>
            </Box>
            {tasks}
            {table && doneField && <AddTaskForm table={table} />}
        </div>
    );
}
Example #5
Source File: UpdateRecordsApp.js    From apps-update-records with MIT License 5 votes vote down vote up
function UpdateSelectedRecordsButton({tableToUpdate, fieldToUpdate, selectedRecordIds}) {
    // Triggers a re-render if records values change. This makes sure the record values are
    // up to date when calculating their new values.
    const records = useRecords(tableToUpdate, {fields: [fieldToUpdate]});

    // Track whether we're currently in the middle of performing an update.
    // We use this to disable the button during an update.
    // We also use this to show the correct number of records being updated on
    // the button: when the update starts, we store the number here. If the user
    // changes their selected records during the update, the number shown on the
    // button will remain accurate.
    const [numRecordsBeingUpdated, setNumRecordsBeingUpdated] = useState(null);

    const isUpdateInProgress = numRecordsBeingUpdated !== null;

    let buttonText;
    const recordsText = `record${selectedRecordIds.length === 1 ? '' : 's'}`;
    if (isUpdateInProgress) {
        buttonText = `Updating ${numRecordsBeingUpdated} ${recordsText}`;
    } else {
        buttonText = `Click to update ${selectedRecordIds.length} ${recordsText}`;
    }

    // Prepare the updates that we are going to perform. (Required to check permissions)
    // We need to get all of the selected records to get the current values of numberField.
    // .filter narrows the list of all records down to just the records with id
    // in selectedRecordIdSet.
    const selectedRecordIdsSet = new Set(selectedRecordIds);
    const recordsToUpdate = records.filter(record => selectedRecordIdsSet.has(record.id));

    const updates = recordsToUpdate.map(record => ({
        id: record.id,
        fields: {
            // Here, we add 1 to the current value, but you could extend this to support
            // different operations.
            // [fieldToUpdate.id] is used to use the value of fieldToUpdate.id as the key
            [fieldToUpdate.id]: record.getCellValue(fieldToUpdate) + 1,
        },
    }));

    // Disable the button if any of these are true:
    // - an update is in progress,
    // - no records are selected,
    // - the user doesn't have permission to perform the update.
    // (Phew!)
    const shouldButtonBeDisabled =
        isUpdateInProgress ||
        selectedRecordIds.length === 0 ||
        !tableToUpdate.hasPermissionToUpdateRecords(updates);

    return (
        <Button
            variant="primary"
            onClick={async function() {
                // Mark the update as started.
                setNumRecordsBeingUpdated(updates.length);

                // Update the records!
                // await is used to wait for all of the updates to finish saving
                // to Airtable servers. This keeps the button disabled until the
                // update is finished.
                await updateRecordsInBatches(tableToUpdate, updates);

                // We're done! Mark the update as finished.
                setNumRecordsBeingUpdated(null);
            }}
            disabled={shouldButtonBeDisabled}
        >
            {buttonText}
        </Button>
    );
}
Example #6
Source File: index.js    From apps-wikipedia-enrichment with MIT License 5 votes vote down vote up
function WikipediaEnrichmentApp() {
    const base = useBase();

    const table = base.getTableByName(TABLE_NAME);
    const titleField = table.getFieldByName(TITLE_FIELD_NAME);

    // load the records ready to be updated
    // we only need to load the word field - the others don't get read, only written to.
    const records = useRecords(table, {fields: [titleField]});

    // keep track of whether we have up update currently in progress - if there is, we want to hide
    // the update button so you can't have two updates running at once.
    const [isUpdateInProgress, setIsUpdateInProgress] = useState(false);

    // check whether we have permission to update our records or not. Any time we do a permissions
    // check like this, we can pass in undefined for values we don't yet know. Here, as we want to
    // make sure we can update the summary and image fields, we make sure to include them even
    // though we don't know the values we want to use for them yet.
    const permissionCheck = table.checkPermissionsForUpdateRecord(undefined, {
        [EXTRACT_FIELD_NAME]: undefined,
        [IMAGE_FIELD_NAME]: undefined,
    });

    async function onButtonClick() {
        setIsUpdateInProgress(true);
        const recordUpdates = await getExtractAndImageUpdatesAsync(table, titleField, records);
        await updateRecordsInBatchesAsync(table, recordUpdates);
        setIsUpdateInProgress(false);
    }

    return (
        <Box
            // center the button/loading spinner horizontally and vertically.
            position="absolute"
            top="0"
            bottom="0"
            left="0"
            right="0"
            display="flex"
            flexDirection="column"
            justifyContent="center"
            alignItems="center"
        >
            {isUpdateInProgress ? (
                <Loader />
            ) : (
                <Fragment>
                    <Button
                        variant="primary"
                        onClick={onButtonClick}
                        disabled={!permissionCheck.hasPermission}
                        marginBottom={3}
                    >
                        Update summaries and images
                    </Button>
                    {!permissionCheck.hasPermission &&
                        // when we don't have permission to perform the update, we want to tell the
                        // user why. `reasonDisplayString` is a human-readable string that will
                        // explain why the button is disabled.
                        permissionCheck.reasonDisplayString}
                </Fragment>
            )}
        </Box>
    );
}
Example #7
Source File: sync-data.js    From neighbor-express with MIT License 5 votes vote down vote up
export function SyncData() {
  const messagesTable = useBase().getTable(DESTINATION_TABLE);
  const records = useRecords(messagesTable);
  const [completed, setCompleted] = useState(false);
  const [syncing, setSyncing] = useState(false);

  const [succesfulCount, setSuccesfulCount] = useState(0);
  const [syncError, setSyncError] = useState(null);

  async function syncData() {
    setCompleted(false);
    setSyncing(true);

    const { total, err } = await SyncVolunteerData(messagesTable, records);
    setSuccesfulCount(total);
    setSyncError(err);

    setSyncing(false);
    setCompleted(true);
  }

  return (
    <Box>
      <h2> Sync Galaxy Digital Data </h2>
      {syncing && !completed && <Loader />}
      {!syncing && (
        <>
          {completed && !syncError && (
            <p>Successfully updated all volunteer data</p>
          )}
          {completed && syncError && (
            <p>
              `Sync completed for ${succesfulCount} with error ${syncError}`
            </p>
          )}
          <Box
            margin={2}
            display="flex"
            flexDirection="row"
            justifyContent="flex-start"
            alignItems="center"
          >
            <Button
              marginX={2}
              variant="primary"
              onClick={syncData}
              disabled={syncing}
            >
              Sync
            </Button>
          </Box>
        </>
      )}
    </Box>
  );
}
Example #8
Source File: index.js    From apps-print-records with MIT License 4 votes vote down vote up
// Renders a single record from the Collections table with each
// of its linked Artists records.
function Record({record}) {
    const base = useBase();

    // Each record in the "Collections" table is linked to records
    // in the "Artists" table. We want to show the Artists for
    // each collection.
    const linkedTable = base.getTableByName('Artists');
    const linkedRecords = useRecords(
        record.selectLinkedRecordsFromCell('Artists', {
            // Keep the linked records sorted by their primary field.
            sorts: [{field: linkedTable.primaryField, direction: 'asc'}],
        }),
    );

    return (
        <Box marginY={3}>
            <Heading>{record.name}</Heading>
            <table style={{borderCollapse: 'collapse', width: '100%'}}>
                <thead>
                    <tr>
                        <td
                            style={{
                                whiteSpace: 'nowrap',
                                verticalAlign: 'bottom',
                            }}
                        >
                            <Heading variant="caps" size="xsmall" marginRight={3} marginBottom={0}>
                                On display?
                            </Heading>
                        </td>
                        <td style={{width: '50%', verticalAlign: 'bottom'}}>
                            <Heading variant="caps" size="xsmall" marginRight={3} marginBottom={0}>
                                Artist name
                            </Heading>
                        </td>
                        <td style={{width: '50%', verticalAlign: 'bottom'}}>
                            <Heading variant="caps" size="xsmall" marginBottom={0}>
                                Artworks
                            </Heading>
                        </td>
                    </tr>
                </thead>
                <tbody>
                    {linkedRecords.map(linkedRecord => {
                        // Render a check or an x depending on if the artist is on display or not.
                        const isArtistOnDisplay = linkedRecord.getCellValue('On Display?');
                        return (
                            <tr key={linkedRecord.id} style={{borderTop: '2px solid #ddd'}}>
                                <td style={{textAlign: 'center', whiteSpace: 'nowrap'}}>
                                    <Box
                                        display="inline-flex"
                                        alignItems="center"
                                        justifyContent="center"
                                        width="16px"
                                        height="16px"
                                        marginRight={3}
                                        borderRadius="100%"
                                        backgroundColor={isArtistOnDisplay ? 'green' : 'red'}
                                        textColor="white"
                                    >
                                        <Icon name={isArtistOnDisplay ? 'check' : 'x'} size={12} />
                                    </Box>
                                </td>
                                <td style={{width: '50%'}}>
                                    <Text marginRight={3}>{linkedRecord.name}</Text>
                                </td>
                                <td style={{width: '50%'}}>
                                    <CellRenderer
                                        record={linkedRecord}
                                        field={linkedTable.getFieldByName('Attachments')}
                                    />
                                </td>
                            </tr>
                        );
                    })}
                </tbody>
            </table>
        </Box>
    );
}
Example #9
Source File: index.js    From blocks-usa-map with MIT License 4 votes vote down vote up
function USAMapBlock() {
    const [isShowingSettings, setIsShowingSettings] = useState(false);
    const [selectedState, setSelectedState] = useState(null);

    useSettingsButton(function() {
        setIsShowingSettings(!isShowingSettings);
    });

    const viewport = useViewport();

    const base = useBase();
    const globalConfig = useGlobalConfig();
    const tableId = globalConfig.get('selectedTableId');
    const stateFieldId = globalConfig.get('selectedStateFieldId');
    const colorFieldId = globalConfig.get('selectedColorFieldId');

    const table = base.getTableByIdIfExists(tableId);
    const stateField = table ? table.getFieldByIdIfExists(stateFieldId) : null;
    const colorField = table ? table.getFieldByIdIfExists(colorFieldId) : null;

    // if (table == null || stateField == null || colorField == null) {
    //     setIsShowingSettings(true);
    // }

    const records = useRecords(stateField ? table : null);

    let mapData = null;
    if (stateField !== null && colorField !== null) {
        mapData = getMapData(records, stateField, colorField);
    }

    const mapHandler = (event) => {
        setSelectedState(event.target.dataset.name);
    };

    // If settings is showing, draw settings only
    if (isShowingSettings) {
        return (
            <Box padding={3} display="flex">
                <FormField
                    label="Table"
                    description="Choose the table you want to your State data to come from."
                    padding={1}
                    marginBottom={0}
                >
                    <TablePickerSynced globalConfigKey="selectedTableId" />
                </FormField>
                <FormField
                    label="State Field"
                    description='The State field will select a state by either abbreviation ("NJ") or name ("New Jersey")'
                    marginBottom={0}
                    padding={1}
                >
                    <FieldPickerSynced
                        table={table}
                        globalConfigKey="selectedStateFieldId"
                        placeholder="Pick a 'state' field..."
                        allowedTypes={[FieldType.SINGLE_LINE_TEXT, FieldType.SINGLE_SELECT, FieldType.MULTIPLE_RECORD_LINKS, FieldType.MULTIPLE_LOOKUP_VALUES]}
                    />
                </FormField>
                <FormField
                    label="Color Field"
                    marginBottom={0}
                    description="Choose the state color using either a text field which describes the color name, or a single select."
                    padding={1}
                >
                    <FieldPickerSynced
                        table={table}
                        globalConfigKey="selectedColorFieldId"
                        placeholder="Pick a 'color' field..."
                        allowedTypes={[FieldType.SINGLE_LINE_TEXT, FieldType.SINGLE_SELECT, FieldType.MULTIPLE_LOOKUP_VALUES, FieldType.NUMBER]}
                    />

                {colorField != null && colorField.type === FieldType.NUMBER &&
                    <text>You have selected a numeric type; state colors will be normalized to the maximum value in your field.</text>
                }
                </FormField>
            </Box>
        )
    }
    // otherwise draw the map.
    return (
        <div>
            <Box border="default"
                 backgroundColor="lightGray1"
                //  padding={}
                 >
                {/* TODO Allow selected state to show a column of data. */}
                {/* {selectedState
                    ? <SelectedState selected={selectedState}/>
                    : <div>Click to select a state</div>
                } */}
                <USAMap
                    title="USA USA USA"
                    width={viewport.size.width}
                    height={viewport.size.height - 5}
                    customize={mapData}
                    onClick={mapHandler}
                />
            </Box>
        </div>
    )
}
Example #10
Source File: send-messages.js    From neighbor-express with MIT License 4 votes vote down vote up
export function SendMessagesStep({ previousStep }) {
  const [completed, setCompleted] = useState(false);
  const [sending, setSending] = useState(false);
  const [progress, setProgress] = useState(0);

  const messagesTable = useBase().getTable("Messages");
  const statusField = messagesTable.getFieldByName("Status");
  const queuedOption = statusField.options.choices.find(
    (c) => c.name == "Queued"
  );

  const messagesToSend = useRecords(messagesTable).filter(
    (m) => m.getCellValue("Status").name === "Queued"
  );

  async function sendMessages() {
    setCompleted(false);
    setSending(true);
    const total = messagesToSend.length;
    let i = 0;
    for (const messageToSend of messagesToSend) {
      const response = await sendMessage(messageToSend);

      if (response.status === 202) {
        messagesTable.updateRecordAsync(messageToSend.id, {
          Status: { name: "Sent" },
          "Sent Time": new Date(),
        });
      } else {
        messagesTable.updateRecordAsync(messageToSend.id, {
          Status: { name: "Errored" },
        });
      }
      i += 1;
      setProgress(i / total);
    }
    setSending(false);
    setCompleted(true);
  }

  async function goBack() {
    // Clear any non-persistent data before leaving
    setCompleted(false);
    setSending(false);
    setProgress(0);
    previousStep();
  }

  return (
    <Box>
      <h2> Step 2: Send emails </h2>
      {(sending || completed) && <ProgressBar progress={progress} />}
      {completed && <p> Successfully sent all messages </p>}
      {messagesToSend.length === 0 ? (
        <p>
          {" "}
          There are no messages with status{" "}
          <ChoiceToken choice={queuedOption} marginRight={1} />{" "}
        </p>
      ) : (
        <>
          <Box
            margin={2}
            display="flex"
            flexDirection="row"
            justifyContent="flex-start"
            alignItems="center"
          >
            <Text>
              {" "}
              The following {messagesToSend.length} messages will be sent:
            </Text>
            <Button
              marginX={2}
              variant="primary"
              onClick={sendMessages}
              disabled={sending}
            >
              Send All Messages
            </Button>
          </Box>
          <Box height="300px" border="thick" backgroundColor="lightGray1">
            <RecordCardList
              records={messagesToSend}
              fields={["Email type", "Recipient", "Delivery"].map((f) =>
                messagesTable.getFieldByName(f)
              )}
            />
          </Box>
        </>
      )}
      <Button onClick={goBack}> Go Back </Button>
    </Box>
  );
}