I have the following TypeScript code. I simplified/remove as much as I could.
interface DataPullingPageState
{
loading: boolean;
displayedEntries: string[];
};
export class EntriesPageOne extends React.Component<{}, DataPullingPageState>
{
constructor(props: any)
{
super(props);
this.state = { loading: false, displayedEntries: [] };
}
async componentDidMount()
{
this.setState({ loading: true });
const entries = await api.loadAll();
this.setState({ loading: false, displayedEntries: entries });
}
render()
{
if (this.state.loading)
{
return <div>loading</div>;
}
else if (this.state.displayedEntries.length === 0)
{
return <div>nothing found</div>;
}
else
{
return this.state.displayedEntries.map((entry, i) => <div key={i}>{entry}</div>);
}
}
}
export class EntriesPageTwo extends React.Component<{}, DataPullingPageState>
{
constructor(props: any)
{
super(props);
this.state = { loading: false, displayedEntries: [] };
}
async componentDidMount()
{
this.setState({ loading: true });
const param = "my param";
const entries = await api.loadByStringParam(param);
this.setState({ loading: false, displayedEntries: entries });
}
render()
{
if (this.state.loading)
{
return <div>loading</div>;
}
else if (this.state.displayedEntries.length === 0)
{
return <div>nothing found</div>;
}
else
{
return this.state.displayedEntries.map((entry, i) => <div key={i}>{entry}</div>);
}
}
}
export class EntriesPageThree extends React.Component<{}, DataPullingPageState>
{
constructor(props: any)
{
super(props);
this.state = { loading: false, displayedEntries: [] };
}
async componentDidMount()
{
this.setState({ loading: true });
const param = 123;
const entries = await api.loadByNumberParam(param);
this.setState({ loading: false, displayedEntries: entries });
}
render()
{
if (this.state.loading)
{
return <div>loading</div>;
}
else if (this.state.displayedEntries.length === 0)
{
return <div>nothing found</div>;
}
else
{
return this.state.displayedEntries.map((entry, i) => <div key={i}>{entry}</div>);
}
}
}
As you can see it's three different components that all display the same but have three different ways of loading it.
I'd like to know how I can make only one component out of those three. I've already heard about HoC but don't know if they suit my case.
Yes you can HoC let's simplify your code a bit:
class EntriesPage extends React.Component {
// you don't need state for loading
render() {
const { loading, entries } = this.props
}
}
EntriesPage.defaultProps = { loading: true, entries: [] }
const withEntries = (apiCall) => (Page) => {
return async (props) => {
const entries = await apiCall()
<Page {...props} loading={false} entries={entries} />
}
}
Now you can compose first page like this
// PageOne
export default withEntries(api.loadAll)(EntriesPage)
// PageTwo
export default withEntries(() => api.loadByStringParam('param'))(EntriesPage)
// PageThree
export default withEntries(() => api.loadByNumberParam(123))(EntriesPage)
This will create HoC which accepts dynamic fetching method and pass the result as prop to the final component. Hope this helps
You can even expose params to the final component by changing it to something like this
const withEntries = (apiCall) => (Page) => {
return async (props) => {
const { fetchParam, ...rest } = props
const entries = await apiCall(fetchParam)
<Page {...rest} loading={false} entries={entries} />
}
}
// EntriesPageComposed.tsx
export default withEntries(api.loadByStringParam)(EntriesPage)
<EntriesPageComposed fetchParams={123} />
Or you can even make it completely isolated without HoC and pass everything as prop and make "data loader" component, which is quite common pattern in React apps, which will act only as loader for preparing next props.
const ComposedComponent = async (props) => {
const { fetchMethod, fetchParam, ...rest } = props
const entries = await fetchMethod(fetchParam)
return (
<EntriesPage {...rest} loading={false} entries={entries} />
)
}
<ComposedComponent fetchMethod={api.loadByStringParam} fetchParam={'param'} />
In this way you have initial implementation isolated and you can add new fetch methods on the fly just by passing a prop.