facebook / react

The library for web and native user interfaces.
https://react.dev
MIT License
229.3k stars 46.96k forks source link

Component passing deep down via context api but supported out of the box #28062

Closed prkeshri closed 9 months ago

prkeshri commented 9 months ago

Hi/ Namaste

In certain scenarios, it becomes necessary to have custom components rendered instead of the given one (say via 3rd party libraries). An example can be an existing library rendering some input component, but we want our custom component to be rendered. So, an example can be:

import Component from 'some-3rd-party-library';

<div>
    <Component />
</div>

This component may contain child components and so on, finally leading to render an input like:

function Some_Child_Component_In_Hierarchy() {
    return ....
    ...
        <input type="text"/>
    ...;
}

Now, one way to have some custom component is to pass some prop via context api and let the Some_Child_Component_In_Hierarchy decide the appropriate component to render, Or even pass the component via the context api.

But what if the Some_Child_Component_In_Hierarchy is a third party who have fixed the input component and we have no way to pass the info? In our custom implementations, we can alter the way we wish, but especially in the above case, it is impossible to do.

So, one solution I can think of is to:

  1. Provide the component via context api. One possible implementation:
export type ComponentContextType = Record<any, any>;

export const ComponentContext = React.createContext<ComponentContextType>({});

export const ComponentProvider = ComponentContext.Provider;

export function Override(props: Record<string, any>) {
    // eslint-disable-next-line react/prop-types
    const { children, ...other } = props;
    return <ComponentProvider value={other}>{children}</ComponentProvider>;
}

Now, any component can provide Override for child components via:

<Override h1={My_Custom_H1} TextField={My_Custom_Field} (... other overrides as desired)>
    <Component />
</Override>
  1. React.createElement (or jsx or jsxs etc.) will determine which component to render. One possibility is to by default wrap every component to another component viz:
    
    function new_Jsx(elt, props) {
    const newProps = { Orig: elt, props };
    return original_Jsx(OverridenOrOriginal, newProps);
    }

function OverridenOrOriginal({ Orig, props }) { const { name } = Orig; // In case of Custom component, we have name else Orig is a string const { [name ?? Orig]: Overriden } = useContext(ComponentContext); // We may create a custom hook for this if (Overriden) { return original_Jsx(Overriden, props); } else { return original_Jsx(Orig, props); } }


Well, this can be achieved while making our own components but I am proposing here to make it universal. Please let me know your thoughts.

Thanks & Regards,
Praveen
ritikbanger commented 9 months ago

The proposal looks fine but their are certain things here:

  1. Introducing a universal solution for component overrides might make the codebase harder to maintain in the long run. Developers might need to spend extra time understanding and debugging the context-based overrides, especially if the application grows in complexity.

  2. The proposed solution allows for the override of any component, which might lead to unexpected behavior. It breaks the usual contract between components and their consumers, potentially causing issues when different parts of the application rely on consistent component behavior.

React already provides mechanisms for component customization, such as props and higher-order components. These established patterns might be more familiar to developers and could be preferred over introducing a new context-based approach.

let me know your thoughts.

CC @rickhanlonii

rickhanlonii commented 9 months ago

It's clever, but dependency injection would violate the declarative principles of React since anything anywhere could override anything. If you need to provide a component to a library, that library should provide a prop, and if they dont then you can fork the library.

Closing as won't do, but thanks for the suggestion.