Problems:
Generally when we start creating components in react js which are more likely a container component and that has little or more logic. We generally use many states, effects, and other logics in these containers.
But after certain implementation, we come to know that there is some common requirement that we need to extract out but not all. These kinds of containers are logic agnostic containers/components.
This is the specific requirement that could not be solved with HOC because we are having a container with a common requirement that is spread throughout the container.
Let’s understand the problem with the example.
const LogicAgnostic = () => { const [apiOptions, setApiOptions] = useState([]); useEffect(() => { const updateOptions = async () => { const options = await getOptions(); setApiOptions(options); }; updateOptions(); }, []); const isShowOptions = getIsShowOptions(); return ( <div> {isShowOptions && ( <div> Specialize section But has dependencies in currect component <select value=""> {apiOptions.map(option => { return ( <option value={option} key={option}> {option} </option> ); })} </select> </div> )} <div>Common section</div> </div> ); }; const getOptions = () => { return new Promise(resolve => { resolve([1, 2, 3, 4, 5, 6, 7, 8, 9]); }); }; const getIsShowOptions = () => { return true; };
In the above code, we have “LogicAgnostic” component that is dependent on the “apiOptions” state and suppose it is more tightly coupled and we are bound to not put it upward. “LogicAgnostic” component has a specialized section and the common section.
Now what we want to do is to create a component that could take specialized section as input and we should be able to put it in placeholder.
But again as we know it has a dependency of “apiOptions” and “isShowOptions”.
Solution:
Let’s divide these components in smaller parts
- “ComposableWrapper”
- “ComposableComp”
- “SpecificComp”
“SpecificComp” is specialized section in the above example.
“ComposableComp” will contain the common section in above example and apiOptions logic. It also contains a placeholder where “SpecificComp” will be rendered.
“ComposableWrapper” is the wrapper for “SpecificComp” and “ComposableComp”
const SpecificComp = ({ isShowOptions, apiOptions }) => { if (!isShowOptions) { return null; } return ( <div> Specialize section But has dependencies in currect component <select value=""> {apiOptions.map(option => { return ( <option value={option} key={option}> {option} </option> ); })} </select> </div> ); }; const ComposableWrapper = () => { const isShowOptions = getIsShowOptions(); return ( <> <ComposableComp specificComp={({ apiOptions }) => { return ( <SpecificComp apiOptions={apiOptions} isShowOptions={isShowOptions} /> ); }} /> </> ); }; const ComposableComp = ({ specificComp }) => { const [apiOptions, setApiOptions] = useState([]); const SpecificComp = specificComp; useEffect(() => { const updateOptions = async () => { const options = await getOptions(); setApiOptions(options); }; updateOptions(); }, []); return ( <div> <SpecificComp apiOptions={apiOptions} /> <div>Common section</div> </div> ); };
Achievement:
Our achievement here is the Logic Agnostic component to the Composable component. Here we can compose with any specific component. we just need to pass different specific components.
Another specific achievement is we have partial providing dependencies from the main wrapper( Here is the ComposableWrapper ). And rest we are providing from the child wrapper( Here is the ComposableComp ).
See example code here