DEV Community

Discussion on: Apply condition on specific nested child elements in ReactJS

Collapse
 
ekeijl profile image
Edwin

I'm sorry, but this is not a great solution and I'll try to explain why:

Your mindset about state management in React seems off when I read this sentence:

If we have 10 nested buttons for example, it'll be hassle to iterate over them, also we may add more buttons or change the condition."

There should never be a hassle of iterating over child components to update them. That's the jQuery (or imperative) way of thinking. In React, you should think of a component's state as the single source of truth for that component. In our case, we want to share state between multiple components. The React documentation suggests that we lift our state up to a common ancestor component of all components that want to read the shared state. From this parent, you pass isDisabled as a prop.

This is actually what you call the naive solution. It might be naive, if you have to pass the isDisabled prop through multiple layers of components to reach your button component.

Sidenote: you should just use a regular attribute instead of spreading an object for a single prop:

// Instead of:
<Button {...(isDisable ? { disabled: true } : {})} />
// Use:
<Button disabled={isDisable} />

If you do need to drill props down multiple components, you can use other state management solutions. The easiest one is Context (there are others such as Redux and MobX, but the one you should use depends on the scope of your app). You can read those docs for a full guide, but the core is that your App defines a provider for the state value:

const App = ({children}) => {
   let [isDisabled, setDisabled] = useState(false);

   return <ButtonContext.Provider value={{isDisabled, setDisabled}}>{children}</App>;
};

Then in your consuming components, you can read the context value:

const Switch = () => {
   let {isDisabled, setDisabled} = useContext(ButtonContext);
   return <button onClick={() => setDisabled(!isDisabled)}>toggle</button>;
}

Then you only need to define your ButtonContext and import it in both components:

const ButtonContext = React.createContext();

Now, if you click the Switch, it will update the ButtonContext value in your App, and all Button components consuming the state will rerender with the updated value.

Boom. Done.

This way, there is no need for useRef, which you should really only use as a last resort for accessing the raw component or DOM node (e.g. when setting focus).

You also create tight coupling between your switch and your button components, because you update the buttons inside your switch. Even worse, it's tightly coupled to the implementation of your button, because you look up the DOM elements. The issue is that you need add a check for each additional element that wants to read the disabled state.

I hope you see the difference. This will become more obvious once your app starts scaling. Also read this article about thinking in React.

Collapse
 
zeyadetman profile image
Zeyad Etman

Thanks for your reply, But what I mean when you get into already written code, and there’s multiple nested ‘Button’ components and you don’t have the ability to change its internal code, as it’s been imported from external package.

Collapse
 
ekeijl profile image
Edwin

Aaaaaah yes, now I get it! Usually this happens if you want to use some NPM module that is not written in React, or a React component that is missing the props in its API to get it to do what you want.

In that case you want to create a wrapper React component that renders the 'external' component. From the wrapper component, you can use refs and useEffect, like in your original post to control the behaviour of the external component.

The point is that you keep the wrapper code that 'knows' about the internals as close as possible to the external package. That way you can still use my solution, where you pass props to the wrapper component and keep all your other components clean. 😀