Components:Component Helpers - bettyblocks/cli GitHub Wiki

Contents

Component Helpers provide convenient objects and Components that make it easier to integrate your Components with the Betty Blocks platform. The following objects are available inside the JSX of a Component:

classes

An object supplied by JSS to apply styling to elements. The keys and values are based on the styles object defined in the Component. Read more about JSS.

options

An object containing the values of the options. The options are defined inside the options of the prefab, and their values are supplied by the builder in the Page Builder sidebar.

If an option) with name 'titleText' was defined in the prefab, its value may be used in JSX like so:

<h1>{options.titleText}</h1>

Options have type { [key: string]: any }.

children

Marks the place to render child components. Read more about child components.

Children have type JSX.Element | JSX.Element[] | undefined.

<B.Icon>

The predefined B.Icon component allows you to render an icon.

This is best used in combination with the icon option.

A className can be given to the Icon component to apply styling.

const { Icon } = B;
const { icon } = options;

<Icon name={icon} className={classes.icon} />;

<B.Link>

The predefined B.Link Component creates a link to another Page. Use it to link to another Betty endpoint by using the ENDPOINT option.

Render the link to the endpoint with the B.Link Component:

const { Link } = B;
const { endpoint } = options;

<div>
  <Link endpoint={endpoint}>Link to a page</Link>
</div>

See the React Router documentation on the Link component for more information.

B.Link is a Component.

B.getVariable()

If you know the id of an Input Variable, use B.getVariable to retrieve e.g. the name of a variable:

const { getVariable } = B;
const { name } = getVariable(id);

This helper is best used in combination with the useParams hook.

B.getVariable() has type (id: string) => Variable.

B.getProperty()

The Builder can specify a Property through a component option of type PROPERTY. Pass the value of this option to B.getProperty to obtain information about the property:

The Property option listed in the prefab might look like:

{
  value: '',
  label: 'Property',
  key: 'property',
  type: 'PROPERTY',
}

Passing the value to B.getProperty returns information about the Property:

const { kind, modelId, name } = B.getProperty(options.property);

You can use this information to interpret the data returned inside a <B.Query>, or to display the Property value differently for certain Property kinds.

B.getProperty has the following type:

B.getProperty: (id: string) => Property;

interface Property {
  kind: string;
  modelId: string;
  name: string;
}

B.getIdProperty()

Use the B.getIdProperty function to access the UUID of the id property of the modelId specified. The UUID of the id property can be used for example in the B.useProperty() hook.

const { getIdProperty } = B;
const { modelId } = options;
const propertyUuid = getIdProperty(modelId); // will return an UUID like: '2087e23cb9a34ae5878b607fd8f23d85'

B.getCustomModelAttribute()

If you know the id of a custom model attribute, use B.getCustomModelAttribute to retrieve e.g. the name of the custom model attribute:

const { getCustomModelAttribute } = B;
const { name } = getCustomModelAttribute(id);

In our set, we use this name for the value of the name attributes of form fields in order to send form data with the expected keys in a custom model.

B.getCustomModelAttribute() has type (id: string) => CustomModelAttribute.

<B.GetAll>

Use the B.GetAll component to retrieve a list of Betty Blocks records. To query your data, you will need to provide the following props:

  • modelId: the id of a Betty Blocks model
  • filter: the filter object to add the filter to the request
  • skip: the offset of the records to be fetched, defaults to 0
  • take: the amount of records to be fetched per page (max 50), defaults to 50

The component must contain a function exposing the following keys:

  • loading is a boolean indicating whether data is being retrieved.
  • error? is either an Error or undefined.
  • data? is undefined, or an object with two keys:
    • results contains a list with Model records, a Model record itself a key-value map from Property name to value.
    • totalCount contains the total number of Model records in the database.
  • refetch? is a function used to refetch the records.

An example where we query a list of records using the B.GetAll component:

const { GetAll } = B;
const { modelId, filter } = options;

<GetAll modelId={modelId} filter={filter} skip={0} take={15}>
  {({ loading, error, data, refetch }) => {
    if (loading) {
      return <span>Loading...</span>;
    }

    if (error) {
      return <span>Something went wrong: {error.message} :(</span>;
    }

    const { totalCount, results } = data;

    return (
      <div>
        <p>There are {totalCount} records.</p>
        <ul>
          {results.map(row => <li key={row.id}>{row.name}</li>)}
        </ul>
      </div>
    );
  }}
</GetAll>

GetAll is based on the Query component from Apollo React, see the Apollo GraphQL documentation on the queries for more information.

<B.GetMe>

Use the B.GetMe component to retrieve data of a logged in user. To query your data, you will need to provide an authenticationProfileId.

The component must contain a function exposing the following keys:

  • loading is a boolean indicating whether data is being retrieved.
  • error? is either an Error or undefined.
  • data? is undefined, or an object with a Model record.

An example where we query user data using the B.GetMe component:

const { GetMe } = B;
const { authenticationProfileId } = options;

<GetMe authenticationProfileId={authenticationProfileId}>
  {({ loading, error, data }) => {
    if (loading) {
      return <span>Loading...</span>;
    }

    if (error) {
      return <span>Something went wrong: {error.message} :(</span>;
    }

    const { id } = data;

    return <p>Fetched a record with ID: {id}.</p>;
  }}
</GetMe>

GetMe is based on the Query component from Apollo React, see the Apollo GraphQL documentation on the queries for more information.

<B.GetOne>

Use the B.GetOne component to retrieve a single Betty Blocks record. To query your data, you will need to provide a model ID and a filter object.

The component must contain a function exposing the following keys:

  • loading is a boolean indicating whether data is being retrieved.
  • error? is either an Error or undefined.
  • data? is undefined, or an object with a Model record.

An example where we query single record using the B.GetOne component:

const { GetOne } = B;
const { modelId, filter } = options;

<GetOne modelId={modelId} filter={filter}>
  {({ loading, error, data }) => {
    if (loading) {
      return <span>Loading...</span>;
    }

    if (error) {
      return <span>Something went wrong: {error.message} :(</span>;
    }

    const { id } = data;

    return (
      <div>
        <p>Fetched a record with ID: {id}.</p>
      </div>
    );
  }}
</GetOne>

GetOne is based on the Query component from Apollo React, see the Apollo GraphQL documentation on the queries for more information.

<B.ModelProvider>

The modelprovider is like a getone except it is used as a wrapper to pass the model to children components.

<ModelProvider key={record.id} value={record} id={model}>
  <InteractionScope model={model}>
     {children}
  </InteractionScope>
</ModelProvider>

<B.Property>

The B.Property component can be used to render record values by property ID when in the scope of B.GetOne component. See the following example:

// Our record
{
  id: 1,
  name: "Betty"
}

// A parent component
const { GetOne } = B;
const { modelId } = options;

return (
  <GetOne modelId={modelId} byId={1}>
    {({ loading, error, data }) => {
      if (error || loading) {
        return null;
      }

      return children;
    }}
  </GetOne>
)

// A child component
const { Property } = B;
const { propertyId } = options;

return <Property id={propertyId} /> // will return 'Betty'

B.useAllQuery()

Use the B.useAllQuery to retrieve a list of Betty Blocks records. To query your data, you will need to provide the following props:

  • modelId: the id of a Betty Blocks model
  • options: an object with the following properties
    • filter: the filter object to add the filter to the request

    • rawFilter: resolved value of the FILTER option. For more info, check the useFilter docs

      NOTE: when provided, it takes precedence over the filter prop.

    • skip: the offset of the records to be fetched, defaults to 0

    • take: the amount of records to be fetched per page (max 50), defaults to 50

    • fetchPolicy: how you want to interact with the cache, default is network-only

    • pollInterval: Specifies the interval in ms at which you want your component to poll for data. Defaults to 0

    • variables: An object containing all of the variables your query needs to execute. Defaults to {}.

    • onError: A callback executed in the event of an error.

    • onCompleted: A callback executed once your query successfully completes.

After being called, the useAllQuery hook returns an object with the following properties:

  • loading is a boolean indicating whether data is being retrieved.
  • error? is either an Error or undefined.
  • data? is undefined, or an object with two keys:
    • results contains a list with Model records, a Model record itself a key-value map from Property name to value.
    • totalCount contains the total number of Model records in the database.
  • refetch? is a function used to refetch the records.

An example where we query a list of records using the B.useAllQuery hook:

const { useAllQuery, useFilter } = B;
const { model, filter, displayError } = options;
const where = useFilter(filter);

const { loading, error, data, refetch } =
model &&
useAllQuery(model, {
rawFilter: where,
skip: 10,
take: 20,
variables: {
    ...(orderBy ? { sort: { relation: sort } } : {}),
},
onCompleted(res) {
    const hasResult = res && res.result && res.result.length > 0;
    if (hasResult) {
    B.triggerEvent('onSuccess', res.results);
    } else {
    B.triggerEvent('onNoResults');
    }
},
onError(resp) {
    if (!displayError) {
    B.triggerEvent('onError', resp);
    }
},
});

useAllQuery is based on the Query hook from Apollo React, see the Apollo GraphQL documentation on the queries for more information.

B.useGetAll()

useGetAll is a deprecated version of useAllQuery, it takes in the same parameters and returns the same result as the useAllQuery please refer to docs for more information.

An example where we query a list of records using the B.useGetAll hook:

const { useGetAll, useFilter } = B;
const { model, filter, displayError } = options;
const where = useFilter(filter);

const { loading, error, data, refetch } =
model &&
useGetAll(model, {
rawFilter: where,
skip: 10,
take: 20,
variables: {
    ...(orderBy ? { sort: { relation: sort } } : {}),
},
onCompleted(res) {
    const hasResult = res && res.result && res.result.length > 0;
    if (hasResult) {
    B.triggerEvent('onSuccess', res.results);
    } else {
    B.triggerEvent('onNoResults');
    }
},
onError(resp) {
    if (!displayError) {
    B.triggerEvent('onError', resp);
    }
},
});

B.useOneQuery()

Use the B.useOneQuery to retrieve a single Betty Blocks record. To query your data, you will need to provide the following props:

  • modelId: the id of a Betty Blocks model
  • options: an object with the following properties
    • filter: the filter object to add the filter to the request

    • rawFilter: resolved value of the FILTER option. For more info, check the useFilter docs

      NOTE: when provided, it takes precedence over the filter prop.

    • fetchPolicy: how you want to interact with the cache, default is network-only

    • onError: A callback executed in the event of an error.

    • onCompleted: A callback executed once your query successfully completes.

After being called, the useOneQuery hook returns an object with the following properties:

  • loading is a boolean indicating whether data is being retrieved.
  • error? is either an Error or undefined.
  • data? is undefined, or an object with a Model record.
  • refetch? is a function used to refetch the records.

An example where we query a list of records using the B.useOneQuery hook:

const { useOneQuery } = B;
const { model, filter, displayError } = options;

const { loading, data, error, refetch } =
    model && useOneQuery(model, {
    filter,
    onCompleted(resp) {
        if (resp && resp.id) {
        B.triggerEvent('onSuccess', resp);
        } else {
        B.triggerEvent('onNoResults');
        }
    },
    onError(resp) {
        if (!displayError) {
        B.triggerEvent('onError', resp);
        }
    },
    }) ||
{};

B.useProperty()

B.useProperty is the hook variant of the B.Property component. It can be used inside the scope of a B.GetOne component to resolve a fetched value.

<B.Text>

Use the B.Text component to interpolate properties with text when in the scope of the B.GetOne component. You can directly pass the component the value of a VARIABLE option.

// A parent component
const { GetOne } = B;
const { modelId } = options;

return (
  <GetOne modelId={modelId} byId={1}>
    {({ loading, error, data }) => {
      if (error || loading) {
        return null;
      }

      return children;
    }}
  </GetOne>
)

// A child component
const { Text } = B;
const { textAndProperties } = options;

return <Text value={textAndProperties} />

B.useText()

Use the B.useText hook to access text from a VARIABLE option directly:

const { useText } = B;
const { textVariablesAndProperties } = options;
const text = useText(textVariablesAndProperties);

return <input value={text} />

<B.Action>

Use the B.Action component to supply a mutation function which runs a Betty Blocks action when called. To run an action you need to provide the following props:

  • actionId: the id of a Betty Blocks action

The component must contain a function exposing two arguments:

  • A function which you can use to run the Betty Blocks action
  • An object which contains the following keys:
    • loading: a boolean indicating whether data is being retrieved
    • error?: either undefined or an GraphQL error object
    • data?: either undefined or the return value set on the Betty Blocks action

An example where we mutate data with a simple form:

const { Action } = B;
// See the ACTION option type on how to manage an actionId
const { actionId } = options;
const [formState, setFormState] = useState({});

return (
  <Action actionId={actionId}>
    {(runAction, { loading, error, data }) => (
      <form
        onSubmit={event => {
          event.preventDefault();

          runAction({
            variables: { input: formState },
          });
        }}
      >
        {error && <span>Error submitting your form</span>}

        {data && <span>Successfully submitted your form</span>}

        <input
          type="text"
          name="first_name"
          onChange={({ target: { value } }) => {
            // keys should match with input variables set on target action
            setFormState(prev => ({ ...prev, first_name: value }));
          }}
        />

        <button type="submit" disabled={loading}>
          Submit
        </button>
      </form>
    )}
  </Action>

B.Action is based on the Mutation components from Apollo React, see the Apollo GraphQL documentation on mutations for more information.

B.getActionInput()

The B.getActionInput helper can be used to get input variable names of actions attached to a parent form. These variable names can be used to set variables of the mutation used in a parent form. See an example below:

// Direct parent form component
const { Action, Children } = B;
const { actionId } = options;
const [formState, setFormState] = useState({});

return (
  <Action actionId={actionId}>
    {(runAction, { loading, error, data }) => (
      <form
        onSubmit={event => {
          event.preventDefault();

          runAction({
            variables: { input: formState },
          });
        }}
      >
        {error && <span>Error submitting your form</span>}

        {data && <span>Successfully submitted your form</span>}

        <Children setFormState={setFormState} loading={loading}>
          {children}
        </Children>

        <button type="submit" disabled={loading}>
          Submit
        </button>
      </form>
    )}
  </Action>
);

// Direct child component
const { getActionInput } = B;
const { actionInputId } = options;
const { loading, setFormState } = parent;
const actionInput = getActionInput(actionInputId);

return (
  <input
    type="text"
    disabled={loading}
    onChange={({ target: { value } }) => {
      setFormState(prev => ({ ...prev, [actionInput.name]: value }));
    }}
  />
);

The above example will mutate the parent components formState. When the form submits the formState is used to set the variables in the runAction mutation.

<B.Children>

The B.Children component can be used to pass values as props down to its children. See the following example:

// A parent component
const { Children } = B;
const [state, setState] = useState(false);

<Children setState={setState}>
  {children}
</Children>

// Direct child component
const { setState } = parent;

return <button type="button" onClick={() => setState(true)} />

B.Children also adds an index prop to its direct children, e.g.:

// Direct child component
return <span>{index}<span>

B.env

The B.env contains the environment. Value is prod in runtime mode and dev in the page builder.

B.useEndpoint()

Use B.useEndpoint to transform the value of an ENDPOINT option into a path.

The following example shows how to use useEndpoint together with useHistory:

const { useEndpoint } = B;
const { endpoint } = options;
const history = useHistory();

history.push(useEndpoint(endpoint));

<B.InteractionScope>

In the case that you are going to render multiple instances of a component from a component set, it is important that you wrap that component in an interaction scope.

This is necessary when you use React's React.createElement api on unknown components or if you render the children of your component in a loop.

const itemComponents = items.map(item => (
    <B.InteractionScope>
        <div className="ListItem">{children}</div>
    </B.InteractionScope>
);

return <div className="MyList">{itemComponents}</div>;

This is done so that each component has unique access to interaction events, otherwise multiple components will override each other until only one component has access.

Context capture

An interaction scope is also capable of capturing the data context within a component that can be passed on to your custom event trigger.

Any global interaction that is connected to your custom trigger will now be able to resolve data properties from where custom event was triggered

<ModelProvider model={model}>
	<B.InteractionScope>
    {context => (
      <Button
      	onClick={ (event) => B.triggerEvent('MyCustomClick', event, context) }
      />
    )}
	</B.InteractionScope>
</ModelProvider>

take note that the child of B.InteractionScope is now a function. The function will recieve the dataContext that the button has access to. This dataContext is then used in the click handler of the button. Note the third argument.

Now whenever the user uses MyCustomClick together with a global interaction, they will now have access to all the propeties that the button has.

You can use this together with SetCurrentRecord to update data containers from a dataTable with buttons

B.useFilter()

Use B.useFilter when you want to change the value of a FILTER option before passing it to the GetAll or GetOne helper. For example:

// Component
const { GetAll, useFilter } = B;
const { modelId, filter } = options;

const resolvedFilter = useFilter(filter);

const customFilter = {
  ...resolvedFilter,
  property: {
    eq: "foo"
  }
}

<GetAll modelId={modelId} rawFilter={customFilter}>
  {({ data }) => {
    return ...;
  }}
</GetAll>

NOTE: Once the value of the FILTER option is resolved by the useFilter helper it should be passed to the rawFilter prop, instead of the filter prop, of Getall and GetOne.

B.useFileUpload()

Use B.useFileUpload for uploading files. Files will be temporarily stored before you submit a form.

// Component
const { useFileUpload } = B;

const [uploadFile, { data, loading, error } = {}] = useFileUpload({
  options: {
    variables: {
      fileList: [...],
      mimeType: [...],
    },
    onError: errorData => {
      ...
    },
    onCompleted: uploadData => {
      ...
    },
  },
});

The returned value will be an array of objects with the keys name and url. When one of the files failed, the reason will be shown in the url field.

NOTE: When the user is behind authentication the rate limiting and max file size is increased.

B.useLogic()

Use the B.useLogic() hook to convert the display logic value to a boolean.

// component
const { useLogic } = B;
const { displayLogic } = options; // this is an option of type DISPLAY_LOGIC

const logic = useLogic(displayLogic);

The return value will be a boolean.

useParams()

The useParams hook can be used to fetch dynamic segments of the URL. Best combined with getVariable() and the VARIABLE option. The next example shows how to dynamically get the value from a /orders/:id endpoint URL.

const { getVariable } = B;
const { variableId } = options;

const variable = getVariable(variableId);

if (variable) {
  const params = useParams();
  const value = params[variable.name];
}

Note: this function only works within the runtime environment. In the builder environment it does nothing.

useLocation()

The useLocation hook can be used to retrieve the current location object.

Note: this function only works within the runtime environment. In the builder environment it does nothing.

useHistory()

The useHistory hook returns an object with several useful methods that read and manipulate the history of the Application. Read more about the history library. Some of the more useful methods include:

  • history.push
  • history.goBack
  • history.goForward

Note: this function only works within the runtime environment. In the builder environment it does nothing.

useState()

Use useState to keep track of state within your component. In the following example, a button toggles the state of a paragraph:

const [enabled, setEnabled] = useState(false);

return (
  <div>
    <p>I'm {enabled ? 'enabled' : 'disabled'}</p>
    <button onClick={() => setEnabled(prevState => !prevState)}>
      {enabled ? 'Disable' : 'Enable'}
    </button>
  </div>
);

useState has type <T>(T) => [T, () => void].

useReducer()

​ Use useReducer when you have complex state logic. It is an alternative to useState. ​

const initialState = { count: 0 };function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error('Specify a valid action type.');
  }
}const [state, dispatch] = useReducer(reducer, initialState);return (
  <>
    {state && <p>{state.count}</p>}
    <button
      type="button"
      onClick={() => {
        dispatch({ type: 'increment' });
      }}
    >
      Count
    </button>
  </>
);

Read more about the useReducer hook. ​

useRef()

​ Use useRef to manage a mutable JavaScript object for the full lifetime of a component. Refs are primarily used as a way to access the DOM via the ref attribute. However it has a lot more use-cases. ​

const input = useRef(null);const handleClick = () => {
  input.current.focus();
};return (
  <>
    <input ref={input} type="text" />
    <button type="button" onClick={handleClick}>
      Focus input
    </button>
  </>
);

Read more about the useRef hook. ​

useEffect()

​ Use useEffect to handle side effects in you components. It accepts a function which is executed after every complete render. ​

const [count, setCount] = useState(0);useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);return (
  <>
    <p>{count}</p>
    <button
      type="button"
      onClick={() => {
        setCount(prevCount => prevCount + 1);
      }}
    >
      Count
    </button>
  </>
);

Read more about the useEffect hook. ​

useCallback()

​ Use useCallback memoize a callback which will only be update when a specified dependency changes. ​

const memoizedCallback = useCallback(
  () => {
    doSomething(a, b);
  },
  [a, b, doSomething],
);

Read more about the useCallback hook.

⚠️ **GitHub.com Fallback** ⚠️