Components:Component Helpers - bettyblocks/cli GitHub Wiki
- classes
- options
- children
- B.Icon
- B.Link
- B.getVariable()
- B.getProperty()
- B.getIdProperty()
- B.getCustomModelAttribute()
- B.GetAll
- B.GetMe
- B.GetOne
- B.ModelProvider
- B.Property
- B.useAllQuery()
- B.useGetAll()
- B.useOneQuery()
- B.useProperty()
- B.Text
- B.useText()
- B.Action
- B.getActionInput
- B.Children
- B.env
- B.InteractionScope
- B.useEndpoint()
- B.useFilter()
- B.useFileUpload()
- B.useLogic()
- useParams()
- useLocation()
- useHistory()
- useState()
- useReducer()
- useRef()
- useEffect()
- useCallback()
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:
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.
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 }
.
Marks the place to render child components. Read more about child components.
Children have type JSX.Element | JSX.Element[] | undefined
.
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} />;
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
.
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
.
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;
}
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'
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
.
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 anError
orundefined
. -
data?
isundefined
, 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.
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 anError
orundefined
. -
data?
isundefined
, 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.
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 anError
orundefined
. -
data?
isundefined
, 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.
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>
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'
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 theFILTER
option. For more info, check the useFilter docsNOTE: 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 isnetwork-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 anError
orundefined
. -
data?
isundefined
, 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.
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);
}
},
});
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 theFILTER
option. For more info, check the useFilter docsNOTE: when provided, it takes precedence over the
filter
prop. -
fetchPolicy
: how you want to interact with the cache, default isnetwork-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 anError
orundefined
. -
data?
isundefined
, 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
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.
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} />
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} />
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?
: eitherundefined
or an GraphQL error object -
data?
: eitherundefined
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.
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.
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>
The B.env
contains the environment. Value is prod
in runtime mode and dev
in the page builder.
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));
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.
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
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
.
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.
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.
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.
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.
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.
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]
.
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.
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.
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.
Use useCallback
memoize a callback which will only be update when a specified dependency changes.
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b, doSomething],
);