Vardan Hambardzumyan
Vardan Hambardzumyan

Reputation: 416

Change parent component background on hover in reactJS

I have following React code

Code

What I would like is to when I hover my "E-commerce" picture App component background should change on "E-commerce" picture background.

So respectively and for other pictures.

I will be very grateful if you help me solve this problem.

Upvotes: 2

Views: 901

Answers (2)

Frank
Frank

Reputation: 2208

Context, according to the React docs, should be used only for truly global state like current user or theme. Using context for components makes them less reusable.

updated code

Your component tree is App -> SolutionBox -> SolutionItem.

You want to "react" to an event in SolutionItem in App but there is SolutionBox inbetween them so you have to thread the event thru SolutionBox to App.

Step 1

Add a prop to SolutionItem called on OnHover, this will be a function call back that any parent component can use to react to changes.

function SolutionsSectionBoxItem({ solutionIMG, onHover }) {
  let callOnHover = state => {
    if (_.isFunction(onHover)) {
      onHover(state);
    }
  };

  return (
    <div className="solutions-section-item-box">
      <img
        src={solutionIMG}
        alt=""
        onMouseEnter={() => {
          callOnHover(true);
        }}
        onMouseLeave={() => {
          callOnHover(false);
        }}
        className="solutions-section-item-img"
      />
    </div>
  );
}

Step 2

Add a prop to SolutionBoxItem called on BGChanged, this will again be a function call back that will be called when any solutionitem onhover happens. This function will take a menuName string and pass either the current menu name or default.

function SolutionsSectionBox({ onBGChanged }) {
  let callBGChanged = menuName => {
    if (_.isFunction(onBGChanged)) {
      onBGChanged(menuName);
    }
  };

  return (
    <div className="solutions-section-box-box">
      <SolutionItem
        solutionIMG={Ecommerce}
        onHover={state => {
          callBGChanged(state === true ? "Ecommerce" : "default");
        }}
      />
      <SolutionItem
        solutionIMG={SalesMarketing}
        onHover={state => {
          callBGChanged(state === true ? "SalesMarketing" : "default");
        }}
      />
      <SolutionItem
        solutionIMG={Analytics}
        onHover={state => {
          callBGChanged(state === true ? "Analytics" : "default");
        }}
      />
      <SolutionItem
        solutionIMG={Middleware}
        onHover={state => {
          callBGChanged(state === true ? "Middleware" : "default");
        }}
      />
    </div>
  );
}

Step 3

In the App component listen for the changes. In here we now set state when ever the mouse enters or leaves a solution item. From here you have to change the background, you are using css to control the background url, this will be harder since you now need css class for each background type. You could use the bgImage state value to change the name of the extra css className like 'AppSalesMarketing', 'AppEcommerce', etc.

    export default function App() {
  const [bgImage, setbgImage] = useState(E);

  const onBGChanged = menuName => {
    setbgImage(menuName);
  };

  return (
    <div className={`App ${bgImage === "default" ? "" : `App${bgImage}`}`}>
      <SolutionBox onBGChanged={onBGChanged} />
    </div>
  );
}

In CSS

Leave the original App class but based on the bgImage value add an additional one using the name of the bgImage + App like below to cascade down the updated background-image value.

.AppEcommerce {
  background-image: url(https://placekitten.com/600/600);
}

.AppSalesMarketing {
  background-image: url(https://placekitten.com/500/800);
}

.AppAnalytics {
  background-image: url(https://placekitten.com/800/500);
}

.AppMiddleware {
  background-image: url(https://placekitten.com/700/700);
}

Extra

I added lodash to test that the incoming props are functions before I call them, it is good to do defensive programming because you never know who may use your component in the future.

let callBGChanged = menuName => {
    if (_.isFunction(onBGChanged)) {
      onBGChanged(menuName);
    }
  };  

Upvotes: 2

Dylan Kerler
Dylan Kerler

Reputation: 2187

Two ways to solve the problem. One is passing down a function to update state, the other is to useContext. In this case it makes sense to use context because you are passing down a function through multiple components that do not care about the function.

First thing to do is make the background image dynamic in the div's style and use context:

// Put this outside the component
export const BackgroundContext = React.createContext(null);

// -- snip
const [backgroundImage, setBackgroundImage] = useState(Ecommerce);
const updateBackgroundImage = newImage => setBackgroundImage(newImage);

// -- snip
<BackgroundContext.Provider value={updateBackgroundImage}>
  <div className="App" style={{ backgroundImage: `url(${backgroundImage})` }}>
  {/* -- snip */}
</BackgroundContext.Provider>

Now in your SolutionsSectionBoxItem component you can import the background context:

import BackgroundContext from "../App";

Then using that context and react's mouseover api, update the selected background image:

  const setBackgroundImage = useContext(BackgroundContext);
  // -- snip
  <img onMouseOver={() => setBackgroundImage(solutionIMG)} {/* -- snip -- */} />

You can read more here: https://reactjs.org/docs/hooks-faq.html#how-to-avoid-passing-callbacks-down

Upvotes: 0

Related Questions