Use TypeScript
Whether we are talking just about react or other libraries, using Typescript will help so much in the efforts to keep your code organized. let’s compare the following Javascript vs Typescript dealing with props types.
import PropTypes from 'prop-types'
function UserCard({ user }) {
return <div className="user-card">
{user.firstname}, {user.lastname}
</div>
}
UserCard.propTypes = {
user: PropTypes.shape({
firstname: PropTypes.string.isRequired,
lastname: PropTypes.string.isRequired
...
})
}
function UserList({ users }) {
return <div className="user-list">
{users.map((user) => <UserCard user={user} />)}
</div>
}
UserList.propTypes = {
users: PropTypes.arrayOf(PropTypes.shape({
firstname: PropTypes.string.isRequired,
lastname: PropTypes.string.isRequired
...
}))
}
interface User {
firstname: String!
lastname: String!
...
}
function UserCard({ user }: { user: User }) {
return <div className="user-card">
{user.firstname}, {user.lastname}
</div>
}
function UserList({ users }: { users: User[] }) {
return <div className="user-list">
{users.map((user) => <UserCard user={user} />)}
</div>
}
Imagine having all your data schemas as interfaces in a single place and reusing them in all the rest of your code. This will not only help you to avoid typing mistakes but also in case you want to change the schema, you should only change it in a single place.
Besides that, many well-known javascript libraries are migrating to Typescript. eg: AdonisJS
Separate Presentational & Container Components
Separating Presentational & Container Components makes our code easier to test and reason about.
Presentational components are concerned with how things look. It receives its data and behavior from parent components.
Container components are concerned with how things work. They provide the data and behavior to presentational or other container components.
Using this approach allows us to reuse the same presentational components with different data and behavior. In addition to that, it makes our code cleaner and much easier to test.
Check the following example with User Component which is used with different containers that provide different data and behavior.
function BuyerContainer() {
return <UserComponent
name="Buyer"
onClick={() => console.log("Buyer Clicked")}
/>
}
function SellerContainer() {
return <UserComponent
name="Seller"
onClick={() => console.log("Seller Clicked")}
/>
}
function UserComponent({ name, onClick }) {
return <div onClick={onClick}>{name}</div>
}
Use React Hooks and Functional Components
Functional Components “referred to as Stateless Components before” are no anymore stateless. thanks to React Hooks, now you can useState
hook to store state into a functional Component. or even use component lifecycle using useEffect
.
Functional components are easy to read and test.
React Core got some other useful hooks that you can explore in
Hooks Reference. The amazing thing is that you can also define your custom hooks.
In the following example, we created a custom react hook called useDebounce. Which is used to limit autocomplete API calls when the input text changes.
import { useEffect } from 'react';
import { debounce } from 'lodash';
export default function useDebounce( fn, delay = 500 ) {
const debounced = useMemoOne( () => debounce( fn, delay ), [
fn,
delay,
] );
useEffect( () => () => debounced.cancel(), [ debounced ] );
return debounced;
}
export default function SearchComponent()
const fetchAutoComplete = useDebounce((e) => {
}, 1000)
return (
<div>
<input type="text" onChange={fetchAutoComplete} />
{...}
</div>
)
}
Besides that, React hooks are a great replacement for **Higher-Order Components (**HoCs).
Styled Component
Styled Component is a library allowing the introduction of Dynamic CSS at the component level. while taking the advantage of ES. It makes your components more predictable and reusable.
Forget about wasting too much time looking for the right class name for an element while trying to avoid using an existing one. With Styled Components ensure that your styles are scoped to the component and auto-generated class names at the build step.
Besides that, it have never been easier to create dynamic CSS. your styles will be generated according props passed to the component. In the following example, the div style is both dependent on the outlined
prop and the global theme.
const Wrapper = styled.div`
border: ${props => props.outlined ? '1px solid' : 'none'};
background: ${props => props.theme.light ? 'black' : 'white'}
`
The last point about styled-components is that It improves the performance by not loading unnecessary styles of unused components.
Slot Fill Library
Let’s you have defined a layout for your react app. then you want to add a widget in the sidebar only for a specific page. If you didn’t consider that case from the beginning It can require a huge change to the layout.
But using a library like ‣ You can just define a Slot at the Sidebar. Then fill that slot with widgets only for particular pages. that way you will avoid passing flags all along the components tree to access the Sidebar.
It has similar behavior to React Portals which is also a great solution for cases like Modals, Tooltips...
import { Slot, Fill, Provider } from 'react-slot-fill';
const Sidebar = (props) =>
<div>
<Slot name="Sidebar.Item" />
</div>
export default Toolbar;
Sidebar.Item = (props) =>
<Fill name="Sidebar.Item">
<button>{ props.label }</button>
</Fill>
const Widget = () =>
[
<Sidebar.Item label="Dashboard Widget" />
];
const Page = ({children}) =>
<Provider>
<Header />
<Sidebar />
<Content>{children}</Content>
<Footer />
</Provider>
const HomePage = () =>
<Page>
a Page without Widjet
</Page>
const DashboardPage = () =>
<Page>
a Page with Widjet
<Widget />
</Page>
Higher-Order Components
Even if React hooks replace HOCs in most cases. HoCs are still a great choice concerning hiding complexity from components like providing multiple props to Page Components or conditional rendering (Private routes, loading status ...)
The following example illustrates how can we encapsulate the complexity of both Private routes and Page common props into reusable HoCs applied to all application pages.
Keep in mind that most HoCs cases can be replaced by React Hooks. and that we can by mistake override props by composed HoCs. So please use HoCs only when necessary to keep page components cleaner. otherwise use React Hooks.
function withPrivateRoute(Component) {
...
return function PrivateRoute(props) {
if (!userConnected) return <Redirect to="/home" />;
return <Component {...props} />;
};
}
function withPageProps(Component) {
...
return function privateRoute(props) {
return <Component
navigation={navigation}
currentPath={currentPath}
currentUser={currentUser}
{...props}
/>;
};
}
function ProfilePage({ navigation, currentPath, currentUser}) {
return <div>
Profile Page
</div>
}
export default withPrivateRoute(withPageProps(ProfilePage))
Error Boundaries
Error boundaries are class components, which catch all errors/exceptions thrown at children's level. When declared at the top level it will allow you to do proper error handling by showing an error message and logging the error at a platform monitoring tool like Sentry.
This way you’ll be the first how catch errors and try fixing them before it impacts the user experience.
Note: ErrorBoundaries
should be declared at class they do not support functional components.
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
logErrorToMySentry(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong ! Contact Admin</h1>;
}
return this.props.children;
}
}
