Feedback Popup Wrapper
Write a wrapper component that triggers a feedback popup.
Environment
Use the environment you are most comfortable with. I recommend using create-react-app to create a local version of the project so that you can inspect the DOM easily from your browser when debugging. Alternatively, you can work from a blank React template in CodeSandbox. Starter code is provided after the problem specification.
Specification
Write a functional wrapper component that accepts as props a submit handler and any number of child components. The wrapper should render the child components, along with a feedback modal that is superimposed on top of the page after the user clicks on the child components more than three times (combined). This behavior is intended to mimic an app prompting a user for a review after they pass a certain level of interaction with the app. The modal should display a sliding range input to accept a rating from 1 to 5, and it should not clear until the user clicks submit. When the rating is submitted, the modal should call the submit handler passed to it via props with the argument as an object of the following type:
{
rating: number
}
For example, if the user slides the range input to 4 and clicks submit, the submit handler should be called with the following object:
{
rating: 4
}
To the right of the slider input, display a string indicating the level of satisfaction that corresponds to the currently selected rating. For example, if the slider is moved all the way to the right (at 5), display “Extremely satisfied.” For values 1 through 4, display the following messages, respectively: “Unsatisfied,” “Somewhat satisfied,” “Satisfied,” and “Very satisfied.” The completed modal is shown below:
The solution shows the wrapper component in action over a bare page with three no-op buttons. The submit handler passed to the wrapper simply generates a browser alert containing the rating, but in a real application, this handler could perhaps send the rating to a database, combine it with a rolling average, etc.
Starter Code
Here is some code to get you started:
import { useState } from "react";
function App() {
return (
<div
style={{
display: "flex",
height: "100vh",
justifyContent: "center",
alignItems: "center",
}}
>
<FeedbackWrapper
onSubmit={(obj) => {
alert(`Rating from modal: ${obj.rating}`);
}}
>
{/* UI composed of three no-op buttons */}
<div style={{ display: "flex", marginBottom: 10 }}>
<button>A button</button>
<button>Another button</button>
</div>
<button style={{ padding: 20 }}>A bigger button</button>
</FeedbackWrapper>
</div>
);
}
const FeedbackWrapper = ({ onSubmit, children }) => {
// YOUR CODE HERE
};
You may want to break up the solution into more than one component. The solution below uses the FeedbackWrapper and one additional component.
Solution
import { useState } from "react";
function App() {
return (
<div
style={{
display: "flex",
height: "100vh",
justifyContent: "center",
alignItems: "center",
}}
>
<FeedbackWrapper
onSubmit={(obj) => {
alert(`Rating from modal: ${obj.rating}`);
}}
>
{/* UI composed of three no-op buttons */}
<div style={{ display: "flex", marginBottom: 10 }}>
<button>A button</button>
<button>Another button</button>
</div>
<button style={{ padding: 20 }}>A bigger button</button>
</FeedbackWrapper>
</div>
);
}
const FeedbackWrapper = ({ onSubmit, children }) => {
const [clicks, setClicks] = useState(0);
return (
<>
{clicks >= 3 && (
<Modal
onSubmit={(val) => {
onSubmit(val);
setClicks(0);
}}
/>
)}
<div
id="feedbackWrapper"
onClick={(event) =>
event.target.id != "feedbackWrapper" && setClicks(clicks + 1)
}
>
{children}
</div>
</>
);
};
const Modal = ({ onSubmit }) => {
const [rating, setRating] = useState(1);
const message = (() => {
switch (rating) {
case 1:
return "Unsatisfied";
case 2:
return "Somewhat satisfied";
case 3:
return "Satisfied";
case 4:
return "Very satisfied";
case 5:
return "Extremely satisfied";
default:
return "error";
}
})();
return (
<div
style={{
zIndex: 100,
backgroundColor: "AntiqueWhite",
width: 500,
height: 200,
position: "absolute",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
}}
>
<div style={{ display: "flex", justifyContent: "center" }}>
<input
type="range"
min={1}
max={5}
value={rating}
onChange={(event) => setRating(parseInt(event.target.value))}
/>
<p style={{ width: 160, marginLeft: 10 }}>{message}</p>
</div>
<button
onClick={() => {
onSubmit({ rating });
setRating(1);
}}
>
Submit
</button>
</div>
);
};
export default App;
The FeedbackWrapper
component renders a modal component that is defined separately, as well as all the children passed via props. Importantly, the children are rendered in a div element that takes advantage of event bubbling by employing an onClick
function that checks the target of any click within the div. As long as the div itself is not the target of the click, the function increments the click count. This logic ensures that we display the modal only after the user interacts three times with subelements of the UI, and it prevents us from counting stray clicks.
We use a state variable in FeedbackWrapper
to count the number of clicks and another in the modal component to store the rating as the user enters it in the slider. The onSubmit handler passed down from the FeedbackWrapper
to the modal component is a function that both triggers the original onSubmit
handler and resets the click count. Finally, I use the zIndex
and position
attributes to superimpose the modal on the UI.