React + Api+ Context - reactjs

I have a simple React app. On the 'home' page you can search movies from an API and add a movie to a list of favorited. I'm using Context to store which movies are on the list and pass it to the 'favorites' page where those items are rendered. It works well up to a point.
Once on the 'favorites' page, when I remove a movie, I would like the page to then show the updated elements. Instead, I have the elements I already had there plus the elements from the updated list.
So let's say my favorited movies were 'spiderman', 'batman' and 'dracula'. when I remove 'dracula' from the list, I suddenly have the cards of 'spiderman', 'batman, 'dracula', 'spiderman'(again) and 'batman'(again).
When I reload the 'favorites' page, it all works as intended. I just would like for it to be updated correctly upon removing the movie.
Any advice?
Here is the code for the Home page, Favorite page, DataContext and the Card component
import React, { createContext, useState, useEffect } from "react";
export const DataContext = createContext();
function DataContextProvider({ children }) {
const [favorited, setFavorited] = useState([]);
useEffect(() => {
const savedMovies = localStorage.getItem("movies");
if (savedMovies) {
setFavorited(JSON.parse(savedMovies));
}
}, []);
useEffect(() => {
localStorage.setItem("movies", JSON.stringify(favorited));
}, [favorited]);
function addToFavorites(id) {
setFavorited((prev) => [...prev, id]);
}
function removeFromFavorited(id) {
const filtered = favorited.filter(el => el != id)
setFavorited(filtered)
}
return (
<DataContext.Provider value={{ favorited, addToFavorites, removeFromFavorited}}>
{children}
</DataContext.Provider>
);
}
export default DataContextProvider;
function Favorites(props) {
const ctx = useContext(DataContext);
const [favoriteMovies, setFavoriteMovies] = useState([]);
useEffect(() => {
const key = process.env.REACT_APP_API_KEY;
const savedMovies = ctx.favorited;
for (let i = 0; i < savedMovies.length; i++) {
axios
.get(
`https://api.themoviedb.org/3/movie/${savedMovies[i]}?api_key=${key}&language=en-US`
)
.then((res) => {
setFavoriteMovies((prev) => [...prev, res.data]);
});
}
}, [ctx.favorited]);
return (
<>
<Navbar />
<main>
<div className="favorites-container">
{favoriteMovies.map((movie) => {
return <Card key={movie.id} movie={movie} />;
})}
</div>
</main>
</>
);
}
function Home(props) {
const [moviesData, setMoviesData] = useState([]);
const [numOfMovies, setNumOfMovies] = useState(10);
const [search, setSearch] = useState(getDayOfWeek());
const [spinner, setSpinner] = useState(true);
const [goodToBad, setGoodToBad] = useState(null);
function getDayOfWeek() {
const date = new Date().getDay();
let day = "";
switch (date) {
case 0:
day = "Sunday";
break;
case 1:
day = "Monday";
break;
case 2:
day = "Tuesday";
break;
case 3:
day = "Wednesday";
break;
case 4:
day = "Thursday";
break;
case 5:
day = "Friday";
break;
case 6:
day = "Saturday";
break;
}
return day;
}
function bestToWorst() {
setGoodToBad(true);
}
function worstToBest() {
setGoodToBad(false);
}
useEffect(() => {
const key = process.env.REACT_APP_API_KEY;
axios
.get(
`https://api.themoviedb.org/3/search/movie?api_key=${key}&query=${search}`
)
.then((res) => {
setMoviesData(res.data.results);
//console.log(res.data.results)
setSpinner(false);
setGoodToBad(null);
});
}, [search]);
return (
<>
<Navbar />
<main>
<form>
<input
type="text"
placeholder="Search here"
id="search-input"
onChange={(e) => {
setSearch(e.target.value);
setNumOfMovies(10);
}}
/>
{/* <input type="submit" value="Search" /> */}
</form>
<div className="sorting-btns">
<button id="top" onClick={bestToWorst}>
<BsArrowUp />
</button>
<button id="bottom" onClick={worstToBest}>
<BsArrowDown />
</button>
</div>
{spinner ? <Loader /> : ""}
<div>
<div className="results">
{!moviesData.length && <p>No results found</p>}
{moviesData
.slice(0, numOfMovies)
.sort((a,b) => {
if(goodToBad) {
return b.vote_average - a.vote_average
} else if (goodToBad === false){
return a.vote_average - b.vote_average
}
})
.map((movie) => (
<Card key={movie.id} movie={movie} />
))}
</div>
</div>
{numOfMovies < moviesData.length && (
<button className="more-btn" onClick={() => setNumOfMovies((prevNum) => prevNum + 6)}>
Show More
</button>
)}
</main>
</>
);
}
export default Home;
function Card(props) {
const ctx = useContext(DataContext);
return (
<div
className={
ctx.favorited.includes(props.movie.id)
? "favorited movie-card"
: "movie-card"
}
>
<div className="movie-img">
<img
alt="movie poster"
src={
props.movie.poster_path
? `https://image.tmdb.org/t/p/w200/${props.movie.poster_path}`
: "./generic-title.png"
}
/>
</div>
<h2>{props.movie.original_title}</h2>
<p>{props.movie.vote_average}/10</p>
<button
className="add-btn"
onClick={() => ctx.addToFavorites(props.movie.id)}
>
Add
</button>
<button
className="remove-btn"
onClick={() => ctx.removeFromFavorited(props.movie.id)}
>
Remove
</button>
</div>
);
}
export default Card;

As mentioned before a lot of things cold be improved (you might want to check some react tutorial beginners related to best practices).
Anyway the main issue your app seems to be your callback after you get the response from the API (so this part):
useEffect(() => {
const key = process.env.REACT_APP_API_KEY;
const savedMovies = ctx.favorited;
for (let i = 0; i < savedMovies.length; i++) {
axios
.get(
`https://api.themoviedb.org/3/movie/${savedMovies[i]}?api_key=${key}&language=en-US`
)
.then((res) => {
setFavoriteMovies((prev) => [...prev, res.data]);
});
}
here you are calling setFavoriteMovies((prev) => [...prev, res.data]); but you actually never reset your favoriteMovies list.
So in your example favoriteMovies is ['spiderman', 'batman', 'dracula']. Then the useEffect callback executes with the array unchanged.
So you are making the requests just for 'spiderman' and 'batman' but your favoriteMovies array is ['spiderman', 'batman', 'dracula'] when the callback is entered (and this is why you end up appending those values to the existing ones and in the end your favoriteMovies == ['spiderman', 'batman', 'dracula', 'spiderman', 'batman'] in your example).
How to fix?
Quick fix would that might be obvious would be to reset the favoriteMovies at the beggining of useEffect. But that would be a extremly bad ideea since setting the state many times is terrible for performance reasons (each setState callback triggers a re-render) as well as for redability. So please don't consider this.
What I would suggest though would be to get all the values in the useEffect callback, put all the new favorite movies data in a variable and at the end of the function change the state in one call with the full updated list.
There are multiple ways to achieve this (async await is the best imo), but trying to alter the code as little as possible something like this should also work:
useEffect(() => {
const key = process.env.REACT_APP_API_KEY;
const savedMovies = ctx.favorited;
const favoriteMoviesPromises = [];
for (let i = 0; i < savedMovies.length; i++) {
favoriteMoviesPromises.push(
axios
.get(`https://api.themoviedb.org/3/movie/${savedMovies[i]}?api_key=${key}&language=en-US`)
.then((res) => res.data)
);
}
Promise.all(favoriteMoviesPromises).then((newFavoriteMovies) =>
setFavoriteMovies(newFavoriteMovies)
);
});
Please note I wasn't able to test this code since I don't have an exact reproduction of the error (so it might need some small adjustments). This code sample is rather a direction for your problem :)
Edit regarding the comment:
Despite the state issue, I would really recommend working on code cleanliness, efficiency and readability.
Examples (I put a few examples in code snippets to avoid a really long comment):
1. `function getDayOfWeek`:
First thing is that you don't need the `day` variable and all the break statement.
You could just return the value on the spot (this would also stop the execution of the function).
So instead of
case 0:
day = "Sunday";
break;
you could have
case 0:
return "Sunday";
Going even further you don't need a switch case at all. You could just create an array
`const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', "Saturday"]`
and just return daysOfWeek[date].
This would result in shorter and easier to read code.
2. Also this code is not really consistent. For example you have
onChange={(e) => {
setSearch(e.target.value);
setNumOfMovies(10);
}}
but also `onClick={bestToWorst}` which is just `function bestToWorst() { setGoodToBad(true) }`.
If this is not reusable you could just use `onClick={() => setGoodToBad(true)}`.
But even if you really want to keep the bestToWorst callback for whatever reason you could at least write and inline function
(something like `const bestToWorst = () => setGoodToBad(true)` and use it the same).
Anyway... From thoose 2 cases (bestToWorst and `Search here` onChange function),
the second one make more sense to be defined outside.
3. The next part is really hard to read and maintain:
{!moviesData.length && <p>No results found</p>}
{moviesData
.slice(0, numOfMovies)
.sort((a,b) => {
if(goodToBad) {
return b.vote_average - a.vote_average
} else if (goodToBad === false){
return a.vote_average - b.vote_average
}
})
.map((movie) => (
<Card key={movie.id} movie={movie} />
))}
Also this code doesn't belong in html.
You should at least put the slice and sort parts in a function.
Going further `if(goodToBad)` and `else if (goodToBad === false)` are also not ideal.
It would be best to use a separate function an example would be something like:
const getFormattedMoviesData = () => {
let formattedMoviesData = moviesData.slice(0, numOfMovies)
if(!goodToBad && goodToBad !== false) return formattedMoviesData;
const getMoviesDifference = (m1, m2) => m1.vote_average - m2.vote_average
return formattedMoviesData.sort((a,b) => goodToBad ? getMoviesDIfference(b,a) : getMoviesDIfference(a,b)
4. DataContext name doesn't suggest anything.
I would propose something more meaningfull (especially for contexts) like `FavoriteMoviesContext`.
In this way people can get an ideea of what it represents when they come across it in the code.
Additionally the context only contains `favorited, addToFavorites, removeFromFavorited`.
So rather than using
`const ctx = useContext(DataContext);`
you could just use
`const {favorited, addToFavorites, removeFromFavorited} = useContext(DataContext);`
and get rid of the ctx variable in your code
Regarding the api:
If the search api returns all the movie data you need you can take it from there and use it in the favorites.
Alternatively it would be great to have an endpoint to return a list of multiple movies
(so send an array of id's in the request and receive all of them).
But this is only possible if the backend supports it.
But otherwise, since the api might contain hundreds of thousands or even millions, having them all stored on the frontside state would be an overkill
(you can in some cases have this type lists stored in a redux state or a react context and filter them on frontend side.
But it won't be efficient for such a big volume of data).
Small conclusion: ignoring the state part there aren't big issues in the code (and for a personal project or for learning might be decent). But if someone else has to work on in or you have to come back on this code after a month might become a nightmare. (especially since it seems like the codebase is not very small)
And people trying to understand your code might find it hard as well (including when you are posting it on stack overflow). I highlighted just a few, but it should point in the right direction, I hope.

First of all, you should review the way you manage the favorite movies and that of what you want to do with them in your app. If you need to make a page to display the list of favorites, I would rather save in localstorage the necessary information for the list (cover, title, year, id, etc) without having to save the whole movie object. This will prevent you from having to call the API for each movie which will be very bad in terms of performance on your application. Also, it will prevent you from having to create another state on the Favorites page so it will solve your problem automatically (I think your problem came from the duplicate state you have).

Related

React with Firestore autocomplete

Using ReactJS, Firestore - Firebase v9.
I have an autocomplete search bar on my web, built with just pure React. The question that I am trying to solve for at least one month is, if I can make this autocomplete input work with Firestore - (user type E.g 'elepha', auto suggestion appears with offer with word elephant - this is what I coded, and with same case, user will click on the suggestion of elephant, and this word elephant will be send to Firestore.)
Cuz there is not any solution on internet, I wonder, if my question is even possible to make, or not.
My simple autocomplete bar code - (animals_data stands for file with animals names)
and I tried to add onClick={pleaseSend} which is basic addDoc function, but when I click on the suggestion, in Firestore will only appear blank input "".
<SearchBar data={animals_data} />
And the filtering code:
function SearchBar({ placeholder, data }) {
const [filteredData, setFilteredData] = useState([]);
const [wordEntered, setWordEntered] = useState("");
const [newAnswer, setAnswer] = useState("")
const [users, setUsers] = useState([]);
const usersCollectionRef = collection(db, "Answers")
const createUser = async () => {
await addDoc(usersCollectionRef, {name: newAnswer}).then(()=>{
window.location.reload()
}).catch((err)=>{
console.log(err)
})
};
const handleFilter = (event) => {
const searchWord = event.target.value;
setWordEntered(searchWord);
const newFilter = data.filter((value) => {
return value.full_name.toLowerCase().includes(searchWord.toLowerCase());
});
if (searchWord === "") {
setFilteredData([]);
} else {
setFilteredData(newFilter);
}
};
const clearInput = () => {
setFilteredData([]);
setWordEntered("");
};
return (
<div className="search">
<div className="searchInputs">
<input
type="text"
placeholder={placeholder}
value={wordEntered}
onChange={handleFilter}
/>
</div>
{filteredData.length !== 0 && (
<div className="dataResult">
{filteredData.slice(0, 15).map((value, key) => {
return (
<a className="dataItem" onClick={createUser} target="_blank">
<p>{value.full_name} </p>
</a>
);
})}
</div>
)}
</div>
);
}
export default SearchBar;
EDIT 1: added code screenshots
web:
Thank you very much for reading.
after analyzing your code, I notice that you don't update the value newAnswer using its setter. Therefore, you should use the setter to update the state on user click and then add the firestorm document. You can do that by either using a button/unmodifiable input field in instead of an anchor tag to store the value of each option, then use this value inside the click handler to update the state and then use a useEffect to update firestore every time the state changes. Let me know if you need help with some code. Please post it below your original question as an edit without changing it.

Remove text by clicking on it - React

I'm trying to start learning react but fail understanding basic logic.
I have a todo list page, which works fine with a strike-through, but if I try to change the strike through to REMOVE instead, my app disappears on click.
Here's my code, hopefully you can understand:
function Note({ notes, note, onClickSetter }) {
const { input, id } = note
const [strikeThrough, setStrikeThrough] = useState(false);
function onNoteClick(event) {
const { value, id } = event.target
//setStrikeThrough((prev) => !prev) - the strike through which is canceled right now
onClickSetter(prev => prev.filter(aNote => aNote.id !== id)) // why this doesn't work?
}
return (
<>
<h1 style={ strikeThrough ? {textDecoration: 'line-through'} : { textDecoration: 'none' }} id={id} onClick={onNoteClick}>{input}</h1>
</>
)
}
a little explanation on my props:
notes - literally the list of notes which comes from a useState on father component (we shouldn't touch this from my understanding of react)
note - self note information
onClickSetter - the other part of useState, the setter one.
So on another words, I have the notes which holds all notes, and onClickSetter which is in another words is setNotes - both part of useState
on top of that I have a note information, because this is a note component
the father component:
function Body() {
const [Notes, setNotes] = useState([])
return (
<div className='notes-body'>
<NewNote onClickSetter={setNotes}/>
{Notes.map((note) => { return <Note key={note.id} notes={Notes} note={note} onClickSetter={setNotes}/>})}
</div>
)
}
function NewNote({ onClickSetter }) {
const [input, setInput] = useState('')
function onInputChange(event) {
const { value } = event.target
setInput(value)
}
function onButtonClick(event) {
onClickSetter((prev) => {
try {
return [...prev, {input: input, id: prev[prev.length-1].id+1}]
}catch{
return [{input: input, id: 0}]
}
})
setInput('')
}
return (
<>
<Input placeholder="add new note" className='note-text' onChange={onInputChange} value={input}/>
<Button className='btn btn-primary add-note' onClick={onButtonClick} />
</>
)
}
The reason is that event.target.id is a string representing a number since all HTML attributes has the string type. Whilst in your data structure, the ID is a number. So, e.g. "1" vs 1. This can be hard to spot sometimes.
The easiest way to fix this is to add a parseInt to the right place to convert the string to a number:
onClickSetter((prev) => prev.filter((aNote) => aNote.id !== parseInt(id)))
However, I also want to mention (and this is more advanced stuff but I like to get people on the right track :) ) that really, you shouldn't pass the whole setter down into the child component, but instead a callback called something like onRemoveNote that accept the note id and the actual filtering/removal would happen in the parent component.
This would be better placement of concerns. For now though, the above will work and I can help you out on stack overflow chat if needed :).

State update doesn't re-render component in ReactJS

I have a component in which I have this useEffect:
const [charactersInfo, setCharactersInfo] = useState(null);
const [page, setPage] = useState(1);
useEffect(() => {
fetch(`https://api/api/character/?page=${page}`)
.then((res) => res.json())
.then((result) => {
setCharactersInfo(result);
});
}, [page]);
whenever my page state updates there is different data coming from the api as expected. but issue is whenever new setCharactersInfo(result) happens, it does not display the new data.
I am passing my setPage state function to this component as a prop:
<PaginationButtons
data={charactersInfo}
updatePage={(number) => {
setPage(number);
}}
/>
This is re-usable component which generates buttons and it works correctly everywhere except this specific component. any suggestions please?
import React, { useState, useEffect } from "react";
import "./PaginationButtons.css";
function PaginationButtons({ data, updatePage }) {
const [buttonsArr, setButtonsArr] = useState([]);
useEffect(() => {
const finalArray = [];
const { info } = data;
// Not the best solution for situations in which
// info.pages is big number(e.x 1000000) but since we know that
// it mostly will be 34 or less so we can just loop through it :)
for (let i = 1; i < info.pages + 1; i++) {
finalArray.push(
<button
className="page_button"
onClick={() => updatePage(i)}
key={Math.random()}
>
{i}
</button>
);
}
setButtonsArr(finalArray);
}, []);
return <div className="button_container">{buttonsArr.map((el) => el)}</div>;
}
export default PaginationButtons;
data prop is an object which contains various of stuff and on the them is the number of pages that should be displayed. in this specific case it 34 for so I use state and useEffect to loop through this number and store buttons in the state array and map it afterwards
You should handle data change in your child component as well.
pass data to useEffect dependency list.
useEffect(() => {
const finalArray = [];
const { info } = data;
// Not the best solution for situations in which
// info.pages is big number(e.x 1000000) but since we know that
// it mostly will be 34 or less so we can just loop through it :)
for (let i = 1; i < info.pages + 1; i++) {
finalArray.push(
<button
className="page_button"
onClick={() => updatePage(i)}
key={Math.random()}
>
{i}
</button>
);
}
setButtonsArr(finalArray);
}, [data]);
This should help you, no need to maintain state. and i see pages is not array its just key value pair.
function PaginationButtons({ data, updatePage }) {
const { info : { pages } } = data;
return (
<div className="button_container">
<button
className="page_button"
onClick={() => updatePage(pages || 0)}
key={pages}
>
{pages || 0}
</button>
</div>
);
}
The useEffect in PaginationButtons is using an empty dependency so it doesn't update when the data prop updates. From what I can tell you don't need the buttonsArr state anyway. It's also anti-pattern to store derived state from props. Just map the data array prop to the buttons.
Using random values is probably the least ideal method of specifying React keys. You can use the mapped array index, but you should use a unique property for each page element, or if there isn't one you should augment the data with a generated GUID.
function PaginationButtons({ data, updatePage }) {
return (
<div className="button_container">
{data.?info?.pages?.map((page, i) => (
<button
className="page_button"
onClick={() => updatePage(i)}
key={i} // <-- or page.id if available
>
{i}
</button>
))}
</div>
);
}
As stated in the other answers, you need to add data as a dependency. Also, you don't need to call map on buttonsArr, as you're not doing anything with its elements. Just use buttonsArr itself

How can I display my API Data from search in React?

So I'm having trouble writing the function to display the API data whenever I search something. I tried to follow tutorials online, but they are using different API's, so the function and setup is completely different from mine.
I have everything working as far as displaying data. I only need my searchCoin function to showcase whatever coin I look up and to display it.
From the API, the name and id show the exact same thing, so you could target the id or name to find the correct coin
Here is my code
function App() {
const [coins, setCoins] = useState([]);
const [search, setSearch] = useState('');
useEffect(() => {
axios
.get(
'https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&order=market_cap_desc&per_page=3&page=1&sparkline=false'
)
.then(res => {
setCoins(res.data);
console.log(res.data);
})
.catch(error => console.log(error));
}, []);
const handleChange = e => {
setSearch(e.target.value);
};
const searchCoin = e => {
e.preventDefault();
I'm trying to fix this code below to work properly
// setSearch(
coins.filter(coin =>
coin.name.toLowerCase().includes(search.toLowerCase())
)
);
};
return (
<div className='coin-app'>
<div className='coin-search'>
<h1>Search a currency</h1>
<form onSubmit={searchCoin}>
<input type='text' onChange={handleChange} />
<button>Search</button>
</form>
</div>
{coins.map(coin => {
return (
<Coin
key={coin.id}
name={coin.name}
price={coin.current_price}
symbol={coin.symbol}
marketcap={coin.total_volume}
volume={coin.market_cap}
image={coin.image}
/>
);
})}
</div>
);
}
React Only Updates What’s Necessary
React DOM compares the element and its children to the previous one, and only applies the DOM updates necessary to bring the DOM to the
desired state. #reactjs.org
Therefore, every time when coins or search change, the App will re-render.
so we can just create a variable that filters using the search value, creating a variable FilteredCoins
const filteredCoins = coins.filter((coin) =>
coin.name.toLowerCase().includes(search.toLowerCase())
Notice that we are using search, which will be different every time the state change.
Then you can just iterate filteredCoins in this manner:
{filteredCoins.map((coin) => (
<Coin
key={coin.id}
name={coin.name}
price={coin.current_price}
symbol={coin.symbol}
marketcap={coin.total_volume}
volume={coin.market_cap}
image={coin.image}
/>
))}
sandbox to demonstrate:
https://codesandbox.io/s/coins-8ukzf
Here are the steps you need to do.
Add another state hook to hold the filtered coins list
const [filteredCoins, setFilteredCoins] = useState(coins);
add a useEffect to update the filteredCoins once the coins have been populated
useEffect(() => {
setFilteredCoins(coins)
} ,[coins])
Use the filteredCoins in your map
{filteredCoins.map((coin) => {
Set a useEffect to update the filteredCoins from the original coins on every search change
useEffect(() => {
setFilteredCoins(
coins.filter(coin =>
coin.name.toLowerCase().includes(search.toLowerCase())
)
)
} ,[search])
Or run it in your searchCoin function like this
const searchCoin = e => {
e.preventDefault();
I'm trying to fix this code below to work properly
setCoins(
coins.filter(coin =>
coin.name.toLowerCase().includes(search.toLowerCase())
)
);
};
This will run everytime search is updated and filter out only the ones that match

How to use a react component as a function?

I'd like to open a materialui dialog and handle the result from javascript to make a simple Yes/no prompt.
Id like it to work something like this (just mockup code to explain)
<MyPromptComponent />
{
MyPromptComponent.show('Do you really want to?').then((result) => alert(result ? 'then do it' : 'walk away') );
}
So the question is; How (if) can I put functions in my component, that I can call from a reference?
If someone knows of an example where something similar is dont I'd appreciate it.
Edit : 11/10/2020
The "problem" with the state way of doing this is that I have to leave the function showing the prompt, having to store temporary variables outside the function. If I could do something like this the code would be much more readable:
{
let tempData = doAProcessForThisFunctionOnly();
let sureResult = confirmDialog.show('Are you sure?');
if(sureResult )
doSomeMoreWithTempData(tempData);
else
doSomeOtherStuff(tempData);
doSomeEndStuff(tempdata);
}
In react I have to do this
{
let tempData = doAProcessForThisFunctionOnly();
tempDataRef.current = tempData;
setShowDialog();
}
onYes = () => {
let workData = tempDataRef.current;
doSomeMoreWithTempData(workData );
doSomeEndStuff(workData)
}
onNo = () => {
let workData = tempDataRef.current;
doSomeOtherStuff(workData );
doSomeEndStuff(workData)
}
doSomeEndStuff = (workData) => {
//Do the stuff here
}
It really seems I need to jump in and out of a lot of functions just to get a simple confirmation and even using variables outside the functon (refs).
That really seems a big step backwards code-wise, to me.
The "vanilla" way of doing this would even let me use the same prompt-dialog component from many different functions. In reactit seems I need a separate confirm-dialog for each case as the "yes"/"no" events are hardcoded per case.
You should control everything with state:
export default function App() {
const [show, setShow] = useState(false);
return (
<div className="App">
<button onClick={() => setShow(true)}>Initiate</button>
<MyPromptComponent
title="Do you really want to?"
show={show}
onConfirm={() => {
setShow(false);
alert("Then do it")
}}
onCancel={() => {
setShow(false);
alert("Walk away")
}}
/>
</div>
);
}
const MyPromptComponent = ({ show, title, onConfirm, onCancel }) => {
return (
<React.Fragment>
{show &&
<div>
Lets pretend this is modal - {title}
<button onClick={() => onConfirm()}>Confirm</button>
<button onClick={() => onCancel()}>Cancel</button>
</div>
}
</React.Fragment>
);
};
Please see sandbox

Resources