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
Related
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).
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>
);
}
How would you add a component inside an useRef object (which is refering to a DOM element)?
const Red = () => {
return <div className="color">Red</div>;
};
const Black = () => {
return <div className="color">Black</div>;
};
const Green = () => {
return <div className="color">Green</div>;
};
const Button = (params) => {
const clickHandler = () => {
let boolA = Math.random() > 0.5;
if (boolA) {
params.containerRef.current.appendChild(<Red />);
} else {
let boolB = Math.random() > 0.5;
if (boolB) {
params.containerRef.current.appendChild(<Black />);
} else {
params.containerRef.current.appendChild(<Green />);
}
}
};
return <button onClick={clickHandler}>Click</button>;
};
export default function App() {
const containerRef = useRef(null);
return (
<div className="App">
<Button containerRef={containerRef} />
<div ref={containerRef} className="color-container">
Color components should be placed here !
</div>
</div>
);
}
params.containerRef.current.appendChild(); -> throws an error. I`ve put it to show what I would like to happen.
Also is what I`m doing an anti-pattern/stupid ? Is there another (smarter) way of achieving the above ?
codesandbox link
edit :
I forgot some important information to add.
Only Button knows and can decide what component will be added.
expecting you want to add multiple colors, something like this would work and don't need the ref:
import { useState } from "react";
import "./styles.css";
const Color = () => {
return <div className="color">Color</div>;
};
const Button = (params) => {
return <button onClick={params.onClick}>Click</button>;
};
export default function App() {
const [colors, setColors] = useState([]);
return (
<div className="App">
<Button onClick={() => setColors((c) => [...c, <Color />])} />
<div className="color-container">
{colors}
</div>
</div>
);
}
It's better to have a state that is changed when the button is clicked.
const [child, setChild] = useState(null);
const clickHandler = () => {
setChild(<Color />);
};
const Button = (params) => {
return <button onClick={params.onClick}>Click</button>;
};
<Button onClick={clickHandler} />
<div className="color-container">
Color components should be placed here !
{child}
</div>
Working sandbox
Edit: Refer to #TheWuif answer if you want multiple Colors to be added upon clicking the button repeatedly
There're several things from your code I think are anti-pattern:
Manipulate the real dom directly instead of via React, which is virtual dom
Render the Color component imperatively instead of declaratively
Here's the code that uses useState (state displayColor) to control whether <Color /> should be displayed
import { useState } from "react";
import "./styles.css";
const Color = () => {
return <div className="color">Color</div>;
};
const Button = (props) => {
return <button onClick={props.clickHandler}>Click</button>;
};
export default function App() {
const [displayColor, setDisplayColor] = useState(false);
const clickHandler = () => {
setDisplayColor(true);
};
return (
<div className="App">
<Button clickHandler={clickHandler} />
<div className="color-container">
Color components should be placed here !{displayColor && <Color />}
</div>
</div>
);
}
Codesandbox
I want to integrate a search functionality in my Next.js app which should look like this:
User types a keyword in searchBar which is inside of Header
After clicking on the search button the keyword should be passed on the SearchResult page, in which I load it dynamically into the youtube-search-api link
The structure looks like this:
Header.js (contains SearchBar component)
_app.js (contains Header and components)
SearchResults.js (inside pages folder, should fetch typed keyword from Header)
I tried it with zustand and it looks like this currently:
Header.js:
import SearchBar from "./SearchBar";
const Header = () => {
return (
<div className={styles.header}>
...
<SearchBar />
...
</div>
);
};
export default Header;
SearchBar.js:
import create from 'zustand';
import { useRouter } from 'next/router';
import Image from 'next/image';
import styles from '../styles/Header.module.scss';
import loupe from '../public/images/loupe.png';
const useStore = create((set) => ({
keyword: '',
setKeyword: (keyword) =>
set((state) => ({
...state,
keyword,
})),
}));
const SearchBar = () => {
const router = useRouter();
const keyword = useStore((state) => state.keyword);
const setKeyword = useStore((state) => state.setKeyword);
const handleClick = (e) => {
e.preventDefault();
router.push('/searchResults');
};
return (
<div className={styles.searchBar}>
<input
type='text'
value={keyword}
placeholder='Suche...'
onChange={(e) => setKeyword(e.target.value)}
/>
<button className={styles.searchBtn} type='submit' onClick={handleClick}>
<Image src={loupe} alt='' />
</button>
</div>
);
};
export default SearchBar;
_app.js:
import Header from '../components/Header';
function MyApp({ Component, pageProps }) {
return (
<>
<Header />
<Component {...pageProps} />
</>
);
}
export default MyApp;
and SearchResults.js:
import { fetchData } from '../lib/utils';
import Moment from 'react-moment';
import { Modal } from 'react-responsive-modal';
import ReactPlayer from 'react-player/youtube';
export default function SearchResults({ videos }) {
console.log(videos);
const [modalIsOpen, setModalIsOpen] = useState(false);
const [modalData, setModalData] = useState(null);
const videoURL = 'https://www.youtube.com/watch?v=' + modalData;
const sortedVids = videos
.sort((a, b) =>
Number(
new Date(b.snippet.videoPublishedAt) -
Number(new Date(a.snippet.videoPublishedAt))
)
)
.filter((vid) => vid.snippet.title.toLowerCase());
return (
<>
<div className={`${styles.playlist_container} ${styles.search}`}>
<div className={styles.main_container}>
<h1>Search results</h1>
{sortedVids
.filter((v) => v.snippet.title !== 'Private video')
.map((vid, id) => {
return (
<div className={styles.item_container}
key={id}>
<div className={styles.clip_container}>
<Image
className={styles.thumbnails}
src={vid.snippet.thumbnails.medium.url}
layout='fill'
objectFit='cover'
alt={vid.snippet.title}
/>
<button
className={styles.playBtn}
onClick={() => {
setModalData(vid.snippet.resourceId.videoId);
console.log(modalData);
setModalIsOpen(true);
}}
>
<Image src='/images/play.svg' width='60' height='60' />
</button>
</div>
<div className={styles.details_container}>
<h3>{vid.snippet.title}</h3>
</div>
</div>
);
})}
</div>
</div>
<div>
<Modal
open={modalIsOpen}
onClose={() => setModalIsOpen(false)}
center
classNames={{
overlay: 'customOverlay',
modal: 'customModal',
overlayAnimationIn: 'customEnterOverlayAnimation',
overlayAnimationOut: 'customLeaveOverlayAnimation',
modalAnimationIn: 'customEnterModalAnimation',
modalAnimationOut: 'customLeaveModalAnimation',
}}
animationDuration={800}
>
<ReactPlayer
playing={true}
url={videoURL}
width='100%'
height='calc(100vh - 100px)'
config={{
youtube: {
playerVars: {
autoplay: 1,
controls: 1,
},
},
}}
/>
</Modal>
</div>
<Footer />
</>
);
}
export async function getStaticProps() {
const keyword = useStore((state) => state.keyword);
const { YOUTUBE_KEY } = process.env;
const uploadsURL = `https://youtube.googleapis.com/youtube/v3/search?part=snippet&channelId=UCbqKKcML7P4b4BDhaqdh_DA&maxResults=50&key=${YOUTUBE_KEY}&q=${keyword}`;
async function getData() {
const uploadsData = fetchData(uploadsURL);
return {
videos: await uploadsData,
};
}
const { videos } = await getData();
return {
revalidate: 86400,
props: {
videos: videos.items,
},
};
}
Would someone please help me out by telling me what I did wrong and how it works in the right way? Thank you guys!!
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