As mentioned by the first part of tutorial from Gatsby:
Components become the base building blocks of your site. Instead of being limited to the building blocks the browser provides, e.g. , you can easily create new building blocks that elegantly meet the needs of your projects.
The basic example should be: instead of creating <button className="primary-button">
, we can create our own <PrimaryButton/>
and use it throughout the website.
Gatsby also has a great notion about hierarchy of components:
React provides some approaches to organise our component, like HOC, renderProps. In this article, I will talk about some common patterns for organizing components with React.
Format HTMl controls into Components HTML native input, button are ugly and we will not use it directly into our website. The best solution, should be creating our own HTML control components and pass variants in props to control attributes, onClick function and switch the output.
const Button = ({ isloading, children, ...otherProps }) => {
return (
<StyledButton {...otherProps}>
{isloading ? <Spinner /> : children}
</StyledButton>
)
}
<Button onClick="callbackFunction">Button</Button>
<Button isloading="true" onClick="callbackFunction">
isLoading
</Button>
<Button disabled onClick="callbackFunction">
disabled
</Button>
A HTML control doesn't live alone in the DOM. It is always accompanied with a Label and error message. For the component <Select/>
, <input type="radio"/>
, it is even more important to combine other HTML controls like <Options>
or multiple radio input.
const Select = props => {
const { options, label, placeholder, error, name } = props
return (
<div>
{label && <label>{label}</label>}
{error && <span>{error}</span>}
<select name={name}>
{placeholder && <option value={-1}>{placeholder}</option>}
{options &&
options.map((option: { value: String, label: String }) => {
const value = option.value ? option.value : option.label || option
return <option>{option.label || value}</option>
})}
</select>
</div>
)
}
<Select
label="Letter"
placeholder="Please choose a letter"
options="[{value: 'a', label: 'a'}, {value: 'b', label: 'b'}]"
/>
It is also possible to create your own component with MaterialUI, and it is a good practice to leave two hooks "onChange" and "value" as props. If you want to control the component outside of component itself. The way how you construct your form control component depends on how you organise your form.
Component is not only HTML tags. Component also contains logic, so that we can pass callback function and variables into the component to make it more complicated.
Component can be used to format data, to initial service, to render children content with condition.
For example, we can use onErrorDidCatch to implement a new layer of component as error boundary.
// Show Component only for certain countries
const CountryFilter = ({children, countries}) => {
const country = useContext(CountryContext)
{countries.includes(country) ? children : null}
}
// usage
<CountryFilter countries={['FR']}>
<CountryText>
</CountryFilter>
class ErrorBoundary extends Component {
constructor(props) {
super(props)
this.state = { error: "" }
}
componentDidCatch(error) {
this.setState({ error })
trackError(error) // if track exist
}
render() {
const { error } = this.state
if (error) {
return <Error />
}
return this.props.children
}
}
// usage
;<ErrorBoundary>
<App />
</ErrorBounday>
Make use of hierarchy of components can help with organising components for all levels.
const ResponsiveLayout = ({children}) => {
<Header/>
{children}
<Footer/>
}
const App = (contextValue) => {
<AppContext.Provider value={contextValue}>
<ResponsiveLayout>
<Switch>
<Route component={PageA} path={routes.a} exact>
<Route component={PageB} path={routes.b}>
</Switch>
</ResponiveLayout>
</AppContext.Provider>
}
RenderProps is like children props, but it is more flexible.
const PaymentLayout = ({render, payment}) => {
return (
<Header/>
{
render(payment)
}
)
}
const PaymentMethod = (payment) => {
...
}
// usage: render all payment methods
...
payments.map(payment => (
<PaymentLayout payment={payment} render={
payment => (<PaymentMethod payment/>)
}/>
))
React provides strategies to organise components, but it doesn't provide components itself.
Because of that, there are lots of third parties React library for controls, like React-Toggle, React-Modal, ...
Material UI provides all components in one library. The more interesting thing is that we can use Material UI to create our own component.
import Menu from "@material-ui/core/Menu"
const ButtonWithMenu = ({ children, content, className }) => {
const buttonRef = useRef(null)
const [isOpen, setIsOpen] = useState(false)
const handleOpen = () => {
setIsOpen(true)
}
const handleClose = () => {
setIsOpen(false)
}
return (
<div>
<button
ref={buttonRef}
className={`${className}`}
onClick={handleOpen}
type="button"
>
{content}
</button>
<Menu anchorEl={buttonRef.current} onClose={handleClose} open={isOpen}>
{children}
</Menu>
</div>
)
}
// usage
;<ButtonWithMenu content={<div>...</div>}>
<MenuItem>...</MenuItem>
</ButtonWithMenu>