Pass function to custom hook - reactjs

This is my custom component. Basically what is does is it shows modal on failure or success of the api calls.
export const useMessageModal = () => {
const [IsModalVisible, setIsModalVisible] = useState(false);
const [Message, setMessage] = useState(null);
return [
() =>
IsModalVisible ? (
<CModal
isVisible={IsModalVisible}
modalMsg={Message}
onPressModal={() => setIsModalVisible(false)} //hideModal
/>
) : null,
() => setIsModalVisible(true), //showModal
msg => setMessage(msg),
];
};
In one of the components, I want to navigate to another page or call some context action on the modal button for that I want to pass some functions to this custom hook.
Does anyone have any idea?
according to Shubham Verma answer, I've updated my code
import React, { useState, useEffect } from "react";
import "./styles.css";
const CModal = ({ onPressModal }) => {
return (
<div
onClick={() => {
console.log("TEst");
onPressModal();
}}
>
Click me to check function call(open console)
</div>
);
};
export const useMessageModal = (customFuntion) => {
const [IsModalVisible, setIsModalVisible] = useState(false);
const [Message, setMessage] = useState(null);
return [
() =>
IsModalVisible ? (
<CModal
isVisible={IsModalVisible}
modalMsg={Message}
onPressModal={customFuntion} //hideModal
/>
) : null,
() => setIsModalVisible(true), //showModal
(msg) => setMessage(msg)
];
};
export default function App() {
const [test2, setTest2] = useState(false);
const [test, check] = useMessageModal(
() => (test2 ? console.log("gjghjgj") : console.log("hello")),
[test2]
);
useEffect(() => {
check();
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={() => [console.log(test2), setTest2(!test2)]}>
test
</button>
{test()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}

You can simply pass function as a arguement ins useMessageModal. Here is the poc:
import React, { useState, useEffect } from "react";
import "./styles.css";
const CModal = ({ onPressModal }) => {
return (
<div
onClick={() => {
console.log("TEst");
onPressModal();
}}
>
Click me to check function call(open console)
</div>
);
};
export const useMessageModal = (customFuntion) => {
const [IsModalVisible, setIsModalVisible] = useState(false);
const [Message, setMessage] = useState(null);
return [
() =>
IsModalVisible ? (
<CModal
isVisible={IsModalVisible}
modalMsg={Message}
onPressModal={customFuntion} //hideModal
/>
) : null,
() => setIsModalVisible(true), //showModal
(msg) => setMessage(msg)
];
};
export default function App() {
const [test, check, anotherCheck] = useMessageModal(() =>
console.log("hello")
);
useEffect(() => {
check();
}, []);
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
{test()}
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
Here is the demo: https://codesandbox.io/s/custom-hook-function-37kc8?file=/src/App.js

Related

Reactjs Debounce search field re-renders component if the field is not empty

I am trying to create a debounce search and initially when the field is empty the component renders after the provided setTimeout delay. But if I continue to search with the existing keyword it re-renders the List component on each key stroke. How to avoid that?
import { useEffect, useState } from 'react';
import useDebounce from './hooks/useDebounce';
import List from './components/List';
const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [query, setQuery] = useState<string | null>("");
let deBounceSearch = useDebounce(query, 2000);
useEffect(() => {
if (deBounceSearch) {
console.log('Searching...');
} else {
console.log('...');
}
}, [deBounceSearch]);
return (
<div className="App">
<input type="text" placeholder='Search anything' onChange={(e) => setQuery(e.target.value)} />
{deBounceSearch !== '' && (
<List />
)}
</div>
);
}
useDebounce.tsx
const useDebounce = (value: any, delay: number) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => { setDebouncedValue(value) }, delay);
return () => {
clearTimeout(handler);
}
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
You can use useMemo to avoid re-render the List component every time query value changes:
const App: React.FC = () => {
const [todo, setTodo] = useState<string>("");
const [query, setQuery] = useState<string | null>("");
const deBounceSearch = useDebounce(query, 2000);
// ->
const cachedList = React.useMemo(() => <List />, [debouncedValue]);
...
return (
<div className="App">
<input type="text" placeholder='Search anything' onChange={(e) => setQuery(e.target.value)} />
{deBounceSearch !== '' && cachedList}
</div>
);
}
You also can take a look at React.memo
hello man this is not the best to use debounce i suggest u try lodash debounce with useMemo.
but for now the solution for your code is that you forgot to clear the timeout on every time the value change.
here the solution:
import { useEffect, useState, useRef } from "react";
import "./styles.css";
const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
const handler = useRef();
useEffect(() => {
if (handler.current) {
clearTimeout(handler.current);
}
handler.current = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler.current);
};
}, [value, delay]);
return debouncedValue;
};
export default function App() {
const [value, setValue] = useState("");
const debounceSearch = useDebounce(value, 2000);
console.log(debounceSearch);
return (
<div className="App">
<input value={value} onChange={(e) => setValue(e.target.value)} />
<h2>Start editing to see some magic happen!</h2>
</div>
);
}
hope this help .

Button component is getting rendered twice every time

I tried to increment the count whenever i click the button. When click the button it is getting rendered twice. But it should be render only once.
Here is my code
https://codesandbox.io/s/async-pine-3z2ty3?file=/src/App.js
import { useCallback, useMemo, useState } from "react";
import Button from "./Button";
export default function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
const MyButton1 = useMemo(
() => <Button handleClick={handleClick} title="Increment Count" />,
[handleClick]
);
const MyButton2 = useMemo(
() => (
<Button handleClick={() => setCount1(count1 + 1)} title="Click here" />
),
[count1]
);
return (
<div className="App">
<div>count : {count}</div>
{MyButton1}
<div>count1 : {count1}</div>
{MyButton2}
</div>
);
}
import React from "react";
const Button = React.memo(({ handleClick, title }) => {
console.log(title);
return <button onClick={handleClick}>{title}</button>;
});
export default Button;
Problem
Your handleClick function changes count
If count changes new handleClick is created
If handleClick changes you create new <Button>
Solution
Remove redundant useMemos
Pass a function to setCount
Remove dependency from useCallback
export default function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const handleClick = useCallback(() => {
setCount((count) => count + 1);
}, []);
return (
<div className="App">
<div>count : {count}</div>
<Button handleClick={handleClick} title="Increment Count" />
</div>
);
}
Now your component will be rendered once at the beginning and never again
If you want to have two buttons, you have to have two callbacks
export default function App() {
const [count, setCount] = useState(0);
const [count1, setCount1] = useState(0);
const handleClick = useCallback(() => {
setCount((count) => count + 1);
}, []);
const handleClick1 = useCallback(() => {
setCount1((count) => count + 1);
}, []);
return (
<div className="App">
<div>count : {count}</div>
<Button handleClick={handleClick} title="Increment Count" />
<div>count : {count}</div>
<Button handleClick={handleClick1} title="Click here" />
</div>
);
}
sandbox
Remove <StrictMode></StrictMode> from your index.js file

react image gallery custom play function

I want create custom play button outside the ImageGallery Carousel and toggle play and pause by accessing play() and pause() methods through refs (NOTE: not renderPlayPauseButton)
import React, { useEffect, useRef } from "react";
import ImageGallery from "react-image-gallery";
import "react-image-gallery/styles/css/image-gallery.css";
const images = [
{
original: "https://picsum.photos/id/1018/1000/600/",
thumbnail: "https://picsum.photos/id/1018/250/150/"
},
{
original: "https://picsum.photos/id/1015/1000/600/",
thumbnail: "https://picsum.photos/id/1015/250/150/"
},
{
original: "https://picsum.photos/id/1019/1000/600/",
thumbnail: "https://picsum.photos/id/1019/250/150/"
}
];
export default function App() {
const playRef = useRef(null);
useEffect(() => {
console.log(playRef);
}, []);
return (
<div className="App">
<button onClick={() => playRef?.current?.pauseOrPlay()}>Play</button>
<ImageGallery
ref={playRef}
items={images}
showThumbnails={false}
showFullscreenButton={false}
showPlayButton={false}
infinite={true}
/>
</div>
);
}
Need help !!
How about this:
Javascript version:
export default function App() {
const playRef = useRef(null);
const [isPlaying, setIsPlaying] = useState(false);
const playOrPause = () => {
setIsPlaying((prev) => {
if (playRef) {
playRef.current[prev ? "pause" : "play"]();
}
return !prev;
});
};
return (
<div className="App">
<ImageGallery ref={playRef} items={images} />
<button onClick={playOrPause}>{isPlaying ? "Pause" : "Play"}</button>
</div>
);
}
Typescript version:
export default function App() {
const playRef = useRef<ImageGallery | null>(null);
const [isPlaying, setIsPlaying] = useState<boolean>(false);
const playOrPause = () => {
setIsPlaying((prev) => {
playRef?.current?.[prev ? "pause" : "play"]();
return !prev;
});
};
return (
<div className="App">
<ImageGallery ref={playRef} items={images} />
<button onClick={playOrPause}>{isPlaying ? "Pause" : "Play"}</button>
</div>
);
}
Here is the example:
Javascript version
Typescript version

Change of icon in favorites

I try to change the icon and add it to favorites when I click on it. It works fine, my icon is changed but it impacts all my images icons instead of one. How can I fix this? Here is my code:
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState(false);
useEffect(() => {
...
}, []);
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => setFavorite(!favorite)}>
{favorite ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
Your problem is favorite state is only true/false value, when it's true, all images will have the same favorite value.
The potential fix can be that you should check favorite based on item.id instead of true/false value
Note that I added updateFavorite function for handling favorite state changes on your onClick
Here is the implementation for multiple favorite items
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState([]);
useEffect(() => {
...
}, []);
const updateFavorite = (itemId) => {
let updatedFavorite = [...favorite]
if(!updatedFavorite.includes(itemId)) {
updatedFavorite = [...favorite, itemId]
} else {
updatedFavorite = updatedFavorite.filter(favoriteItem => itemId !== favoriteItem)
}
setFavorite(updatedFavorite)
}
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => updateFavorite(item.id)}>
{favorite.includes(item.id) ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
Here is the implementation for a single favorite item
import React, { useState, useEffect } from "react";
import AddCircleOutlineIcon from '#mui/icons-material/AddCircleOutline';
import CheckCircleOutlineIcon from '#mui/icons-material/CheckCircleOutline';
import ExpandCircleDownIcon from '#mui/icons-material/ExpandCircleDown';
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const Row = () => {
const [image, setImage] = useState([])
const [favorite, setFavorite] = useState(); //the default value is no favorite item initially
useEffect(() => {
...
}, []);
const updateFavorite = (itemId) => {
let updatedFavorite = favorite
if(itemId !== updatedFavorite) {
updatedFavorite = itemId
} else {
updatedFavorite = null
}
setFavorite(updatedFavorite)
}
const addToFavorite = () => {
...
}
return (
<div >
{image.map((item) => {
return (
<div key={item.id}>
<span>{item.title}</span>
<span>{item.description}</span>
<img src={...} alt={image.title}/>
<div onClick={() => updateFavorite(item.id)}>
{favorite === item.id ? < CheckCircleOutlineIcon onClick={() => addToFavorite()} /> : < AddCircleOutlineIcon onClick={() => addToFavorite()} />}
</div>
)
})}
</div>
);
}
export default Row;
This is what you're looking for based on the code you've provided:
import { useState, useEffect } from "react";
const Item = ({ item }) => {
const [favorite, setFavorite] = useState(false);
const toggleFavorite = () => setFavorite((favorite) => !favorite);
return (
<div>
<span>{item.title}</span>
<span>{item.description}</span>
<span onClick={toggleFavorite>
{favorite ? "[♥]" : "[♡]"}
</span>
</div>
);
};
const Row = () => {
const [items, setItems] = useState([]);
useEffect(() => {
// use setItems on mount
}, []);
return (
<div>
{items.map((item) => <Item item={item} key={item.id} />)}
</div>
);
};
export default Row;
You can break up your code into smaller components that have their own states and that's what I did with the logic that concerns a single item. I've created a new component that has its own state (favorited or not).

How to filter or search for services in another component (React)

NOTE: My Page Card component is working correctly. How can I filter the card page component in the Search component?
I'm new to react, and I don't quite understand how I can accomplish this task.
In the Search component I put it in a fixed way, as I can't filter a component using another.
The Code is summarized for ease.
Card Page
import React, {
useEffect,
useState
} from "react";
import classes from "./boxService.module.css";
import axios from "axios";
function BoxService() {
const [test, SetTest] = useState([]);
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error!");
});
}, []);
return ({
test.map((test, key) => {
<div className={classes.box}
return (
<Grid item xs = {2} key={key} >
<div className={test.name} < div >
<p className={test.description}</p>
</Grid>
);
})}
);
}
export default BoxService;
Seach Page
import React, {
useState,
useEffect
} from "react";
import axios from "axios";
function Search() {
const [searchTerm, setSearchTerm] = useState("");
const [test, SetTest] = useState([]);
//Chamada API
useEffect(() => {
axios
.get("http://localhost:8080/api/test")
.then((response) => {
SetTest(response.data);
})
.catch(() => {
console.log("Error");
});
}, []);
return (
<div>
<input type = "text"
placeholder = "Search..."
onChange = {
(event) => {
setSearchTerm(event.target.value);
}
}/>
{
test.filter((val) => {
if (searchTerm === "") {
return val;
} else if (
val.nome.toLowerCase().includes(searchTerm.toLowerCase())
) {return val;}
}).map((val, key) => {
return ( <div className = "user"
key = {key} >
<p> {val.name} </p> </div>
);
})
} </div>
);
}
export default Search;
Here is an example of how it should/could look like:
import React from "react";
function SearchBox({ setSearchTerm, searchTerm }) {
const handleFilter = (e) => {
setSearchTerm(e.target.value);
};
return (
<>
filter
<input type="search" onChange={handleFilter} value={searchTerm} />
</>
);
}
export default function App() {
const [searchTerm, setSearchTerm] = React.useState("");
const [filteredResults, setFilteredResults] = React.useState([]);
const [results, setResults] = React.useState([]);
React.useEffect(() => {
const fetchdata = async () => {
const randomList = await fetch(`https://randomuser.me/api/?results=50`);
const data = await randomList.json();
const { results } = data;
setResults(results);
};
fetchdata();
}, []);
React.useEffect(() => {
const filterResults = results.filter((item) =>
item.name.last.toLowerCase().includes(searchTerm.toLowerCase())
);
setFilteredResults(filterResults);
}, [searchTerm, results]);
return (
<div className="App">
<SearchBox setSearchTerm={setSearchTerm} searchTerm={searchTerm} />
<div>
<ul>
{filteredResults.map(({ name }, idx) => {
return (
<li key={idx}>
{name.first} {name.last}
</li>
);
})}
</ul>
</div>
</div>
);
}

Resources