I have a simple React App, where I fetch the Flickr Public Feed API and display it. Unfortunately it is mapping the array several times, where I can see repeated photos. The request always returns an array with 20 items with the same pictures, explaining the repetition.
Check the code below:
import React, { Component } from 'react';
import $ from 'jquery';
import PhotoListItem from '../../components/photoListItem';
import Searchbar from '../../components/searchBar';
import ScrollButton from '../../components/scrollButton';
import '../app/index.css';
export default class PhotoApp extends Component {
constructor(props) {
super(props);
this.state = {
photoList: [],
searchTerm: 'cyanotype',
items: 10,
loadingState: false,
}
}
componentDidMount() {
this.getPhotoList();
this.onInfiniteScroll();
}
/* get data from Flickr public feed */
getPhotoList = () => {
const flickrApiPoint = "https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" + this.state.searchTerm;
try {
$.ajax({
url: flickrApiPoint,
dataType: 'jsonp',
data: { format: "json" },
success: function (data) {
this.setState({ photoList: data.items });
}.bind(this)
});
}
catch (err) {
console.log(err);
}
}
/* code for infinite scroll */
onInfiniteScroll = () => {
this.refs.iScroll.addEventListener("scroll", () => {
if (this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >= this.refs.iScroll.scrollHeight - 20) {
this.loadMoreItems();
}
});
}
/* */
displayItems = () => {
var items = [];
for (var i = 0; i < this.state.items; i++) {
items.push(
this.state.photoList.map((photo, index) => {
const author = photo.author.split(/"/)[1];
const authorLink = photo.description.split(/"/)[1]
const description = photo.description.split(/"/)[13]
return (
<PhotoListItem
key={index}
url={photo.media.m}
photoLink={photo.link}
title={photo.title}
author={author}
authorLink={authorLink}
description={description}
tags={photo.tags} />
)
})
);
}
return items;
}
/* */
loadMoreItems = () => {
if (this.state.loadingState) {
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState({ items: this.state.items + 10, loadingState: false });
}, 1000);
}
render() {
return (
<div className='appContainer' ref="iScroll">
<div className='appHeader'>
<h1 className='headerTitle'>Welcome to Flickr Alternative Photography Feed!</h1>
</div>
<div className='gridContainer'>
{this.displayItems()}
</div>
{this.state.loadingState ? <p className='loading'>Loading items...</p> : ""}
</div>
);
}
}
HERE IS THE LIVE EXAMPLE
The problem is around this.displayItems(), but how can I fix this?
Any help is appreciated. Thank you!
You can achieve this by slicing your array by the amount of items you want to show within your JSX :
this.state.photoList.slice(0, this.state.items).map(
You will then have to use the callback version of setState to use the old values of your state and increment what you want to show :
this.setState(old => ({ items: old.items + 2, loadingState: false }));
Fully functional example (using the "full page" option is recommended) :
class PhotoListItem extends React.Component {
render() {
return (
<div className="image-card">
<img className="image-card__image" alt="" src={this.props.url} />
<div className="image-card__body">
<div className="image-title">
<a href={this.props.photoLink}>{this.props.title}</a>
<span className="image-author">
{" "}
by <a href={this.props.authorLink}>{this.props.author}</a>
</span>
</div>
<div className="image-description">
<span className="description">Description:</span>{" "}
{this.props.description}
</div>
<div className="image-tags">
<span className="tags">Tags:</span> {this.props.tags}
</div>
</div>
</div>
);
}
}
class PhotoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
photoList: [],
items: 2,
searchTerm: "cyanotype",
loadingState: false
};
}
componentDidMount() {
this.getPhotoList();
this.onInfiniteScroll();
}
/* get data from Flickr public feed */
getPhotoList = () => {
const flickrApiPoint =
"https://api.flickr.com/services/feeds/photos_public.gne?jsoncallback=?&tags=" +
this.state.searchTerm;
try {
$.ajax({
url: flickrApiPoint,
dataType: "jsonp",
data: { format: "json" },
success: function(data) {
this.setState({ photoList: data.items });
}.bind(this)
});
} catch (err) {
console.log(err);
}
};
/* code for infinite scroll */
onInfiniteScroll = () => {
this.refs.iScroll.addEventListener("scroll", () => {
if (
this.refs.iScroll.scrollTop + this.refs.iScroll.clientHeight >=
this.refs.iScroll.scrollHeight - 20
) {
this.loadMoreItems();
}
});
};
/* */
loadMoreItems = () => {
if (this.state.loadingState) {
return;
}
this.setState({ loadingState: true });
setTimeout(() => {
this.setState(old => ({ items: old.items + 2, loadingState: false }));
}, 1000);
this.getPhotoList();
};
render() {
return (
<div className="appContainer" ref="iScroll">
<div className="appHeader">
<h1 className="headerTitle">
Welcome to Flickr Alternative Photography Feed!
</h1>
</div>
<div className="gridContainer">
{this.state.photoList.slice(0, this.state.items).map((photo, index) => {
const author = photo.author.split(/"/)[1];
const authorLink = photo.description.split(/"/)[1];
const description = photo.description.split(/"/)[13];
return (
<PhotoListItem
key={index}
url={photo.media.m}
photoLink={photo.link}
title={photo.title}
author={author}
authorLink={authorLink}
description={description}
tags={photo.tags}
/>
);
})}
</div>
{this.state.loadingState ? (
<p className="loading">Loading items...</p>
) : (
""
)}
</div>
);
}
}
ReactDOM.render(<PhotoApp />, document.getElementById("root"));
body,
html {
margin: 0;
min-height: 100%;
}
.appContainer {
font-family: "Helvetica", sans-serif;
width: 100%;
height: 100vh;
overflow: auto;
}
.appHeader {
text-align: center;
background-color: #033666;
padding: 1rem;
}
.headerTitle {
color: #fff;
}
.gridContainer {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
padding: 1rem;
grid-gap: 1rem 1rem;
}
.loading {
text-align: center;
color: #033666;
}
#media only screen and (max-width: 320px) {
.appHeader>h1 {
font-size: 1.2rem;
}
}
a,
a:visited {
color: #000;
text-decoration: none;
}
a:hover {
color: #033666;
text-decoration: underline;
}
.image-card {
display: flex;
display: -webkit-box;
display: -moz-box;
display: -ms-flexbox;
display: -webkit-flex;
flex-direction: column;
width: auto;
height: auto;
margin: .5rem;
border-radius: 5px;
box-shadow: 0 5px 15px rgba(0, 0, 0, .15);
background: #fff;
}
.image-card__image {
border-radius: 5px 5px 0 0;
width: 100%;
height: 200px;
object-fit: cover;
}
.image-card__body {
padding: .5rem 1rem 1rem;
}
.image-title {
font-weight: 600;
margin: 0;
word-wrap: break-word;
padding-bottom: .7rem;
cursor: pointer;
}
.image-author {
font-weight: 100;
font-size: .8rem;
cursor: pointer;
}
.image-owner {
margin-top: 0;
font-size: .8rem;
}
.image-date-view-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
}
.image-description {
padding-bottom: .7rem;
font-size: .9rem;
word-wrap: break-word;
}
.tags,
.description {
font-weight: 600;
}
.image-tags {
font-size: .8rem;
word-wrap: break-word;
}
.App {
font-family: sans-serif;
text-align: center;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.0/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.1/umd/react-dom.production.min.js"></script>
<div id="root" />
Related
I hope you all are doing well. I was following this fireship io tutorial in building a chat app in react. I updated how I got the data with hooks instead of how it was done in the video.
The issue is, I have it working where I can send and receive messages, but I can't apply the appropriate CSS style depending on which user sent a message. The odd part is, I'm able to console log the text and the uid as props.
The focus is on const ChatMessage where, depending on the uid of the current user, it will change the CSS styling to mimic that of imessage. Here's an example.
Final Product
I have it set up where I run npm from vs code and I access the app from two of my google accounts. One from firefox, the other from chrome.
I'm really trying to get a hang of firebase react with hooks, anything helps.
Cheers,
Tutorial Source Code: https://github.com/fireship-io/react-firebase-chat
App.js
import "./App.css";
import "firebase/firestore";
import "firebase/auth";
import { useAuthState } from "react-firebase-hooks/auth";
import { db } from "./firebase";
import {
collection,
onSnapshot,
addDoc,
serverTimestamp,
query,
orderBy,
limit,
} from "firebase/firestore";
import { useEffect, useState, useRef } from "react";
import { getAuth, signInWithPopup, GoogleAuthProvider } from "firebase/auth";
const provider = new GoogleAuthProvider();
const auth = getAuth();
const userID = "";
function App() {
const [user] = useAuthState(auth);
return (
<div className="App">
<header>
<h1>SUp# Ch#tZ🫡</h1>
<SignOut />
</header>
<section>{user ? <ChatRoom /> : <SignIn />}</section>
</div>
);
}
const ChatRoom = () => {
const [messages, setMessages] = useState([]);
const [formValue, setFormValue] = useState("");
const messagesRef = collection(db, "messages");
const recentMessage = useRef();
const queryAtts = query(messagesRef, orderBy("createdAt"), limit(25));
//Send Messages
const sendMessage = async (e) => {
e.preventDefault();
const { uid, photoURL } = auth.currentUser;
await addDoc(collection(db, "messages"), {
text: formValue,
createdAt: serverTimestamp(),
uid,
photoURL,
});
setFormValue("");
recentMessage.current.scrollIntoView({ behavior: "smooth" });
};
//Get Messages from Firestore
useEffect(() => {
//console.log(messagesRef);
onSnapshot(queryAtts, (snapshot) => {
setMessages(
snapshot.docs.map((doc) => {
return { id: doc.id, viewing: false, ...doc.data() };
})
);
});
}, []);
return (
<>
<main>
{messages &&
messages.map((msg, i) => (
<>
{console.log("Pre-Chat Msg uid: ", msg.uid)}
<ChatMessage
msg={msg.text}
uid={msg.uid}
photoURL={auth.currentUser.photoURL}
/>
</>
))}
<div ref={recentMessage}></div>
</main>
<form onSubmit={sendMessage}>
<input
value={formValue}
onChange={(e) => setFormValue(e.target.value)}
/>
<button type="submit" disabled={!formValue}>
😶🌫️
</button>
</form>
</>
);
};
const ChatMessage = (props) => {
//console.log("uid in chat msg: ", props.uid);
console.log("chat message photo: ", props.photoURL);
const messageClass = props.uid === auth.currentUser ? "sent" : "recieved";
console.log("useAuthStateHook uid: ", props.uid);
return (
<div className={`message ${messageClass}`}>
<img
src={
props.photoURL || "https://xsgames.co/randomusers/avatar.php?g=pixel"
}
alt={"broken lol"}
/>
<p key={props.uid}>{props.msg}</p>
</div>
);
};
const SignIn = () => {
const useSignInWithGoogle = () => {
signInWithPopup(auth, provider);
};
return (
<>
<button onClick={useSignInWithGoogle}>SignIn W/ Google</button>
<p>
Do not violate the community guidelines or you will be banned for life!
</p>
</>
);
};
const SignOut = () => {
return (
auth.currentUser && (
<button className="sign-out" onClick={() => auth.signOut()}>
Sign Out
</button>
)
);
};
export default App;
App.css
body {
background-color: #282c34;
}
.App {
text-align: center;
max-width: 728px;
margin: 0 auto;
}
.App header {
background-color: #181717;
height: 10vh;
min-height: 50px;
color: white;
position: fixed;
width: 100%;
max-width: 728px;
top: 0;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 99;
padding: 10px;
box-sizing: border-box;
}
.App section {
display: flex;
flex-direction: column;
justify-content: center;
min-height: 100vh;
background-color: rgb(40, 37, 53);
}
main {
padding: 10px;
height: 80vh;
margin: 10vh 0 10vh;
overflow-y: scroll;
display: flex;
flex-direction: column;
}
main::-webkit-scrollbar {
width: 0.25rem;
}
main::-webkit-scrollbar-track {
background: #1e1e24;
}
main::-webkit-scrollbar-thumb {
background: #6649b8;
}
form {
height: 10vh;
position: fixed;
bottom: 0;
background-color: rgb(24, 23, 23);
width: 100%;
max-width: 728px;
display: flex;
font-size: 1.5rem;
}
form button {
width: 20%;
background-color: rgb(56, 56, 143);
}
input {
line-height: 1.5;
width: 100%;
font-size: 1.5rem;
background: rgb(58, 58, 58);
color: white;
outline: none;
border: none;
padding: 0 10px;
}
button {
background-color: #282c34; /* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
cursor: pointer;
font-size: 1.25rem;
}
button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.sign-in {
color: #282c34;
background: white;
max-width: 400px;
margin: 0 auto;
}
ul, li {
text-align: left;
list-style: none;
}
p {
max-width: 500px;
margin-bottom: 12px;
line-height: 24px;
padding: 10px 20px;
border-radius: 25px;
position: relative;
color: white;
text-align: center;
}
.message {
display: flex;
align-items: center;
}
.sent {
flex-direction: row-reverse;
}
.sent p {
color: white;
background: #0b93f6;
align-self: flex-end;
}
.received p {
background: #e5e5ea;
color: black;
}
img {
width: 40px;
height: 40px;
border-radius: 50%;
margin: 2px 5px;
}
I've tried maneuvering where the auth.currentUser was called. I also reformatted my functions from the function to const format, but I don't believe that makes much of a difference.
It seems that in your ChatMessage component, the messageClass will get a value based on if props.uid equals to uid of the current user.
If this is the goal, you can set it like:
const messageClass = props.uid === auth.currentUser?.uid ? "sent" : "recieved";
Instead of:
const messageClass = props.uid === auth.currentUser ? "sent" : "recieved";
Because auth.currentUser is the object that has some user data properties such as uid.
Here's my App.js
import { useState } from "react";
import { nanoid } from "nanoid";
import NotesList from "./Components/NotesList";
import Search from "./Components/Search";
import Header from "./Components/Header";
const App = () => {
const [notes, setNotes] = useState([
{
id: nanoid(),
text: "This is my first note!",
date: "05/28/2022",
},
{
id: nanoid(),
text: "This is my second note!",
date: "05/28/2022",
},
{
id: nanoid(),
text: "This is my third note!",
date: "05/28/2022",
},
]);
const [searchText, setSearchText] = useState("");
const addNote = (text) => {
const date = new Date();
const newNote = {
id: nanoid(),
text: text,
date: date.toLocaleDateString(),
};
const newNotes = [...notes, newNote];
setNotes(newNotes);
};
const updateNote = (id, text) => {
const updatedNote = {
text: text,
};
const newNotes = notes.map((notes) =>
notes.id === id ? updatedNote : notes
);
setNotes(newNotes);
};
const deleteNote = (id) => {
const newNotes = notes.filter((notes) => notes.id !== id);
setNotes(newNotes);
};
return (
<div className="container">
<Header />
<Search handleSearchNote={setSearchText} />
<NotesList
notes={notes.filter((note) =>
note.text.toLowerCase().includes(searchText)
)}
handleAddNote={addNote}
handleUpdateNote={updateNote}
handleDeleteNote={deleteNote}
/>
</div>
);
};
export default App;
My Note.js
import { MdUpdate, MdDeleteForever } from "react-icons/md";
const Note = ({ id, text, date, handleDeleteNote, handleUpdateNote }) => {
return (
<div className="note">
<span>{text}</span>
<div className="note-footer"></div>
<small>{date}</small>
<MdUpdate
onClick={() => handleUpdateNote(text)}
className="delete-icon"
size="1.3em"
/>
<MdDeleteForever
onClick={() => handleDeleteNote(id)}
className="delete-icon"
size="1.3em"
/>
</div>
);
};
export default Note;
And my NoteLIst.js
import Note from "./Note";
import AddNote from "./AddNote";
const NotesList = ({
notes,
handleAddNote,
handleDeleteNote,
handleUpdateNote,
}) => {
return (
<div className="notes-list">
{notes.map((note) => (
<Note
id={note.id}
text={note.text}
date={note.date}
handleUpdateNote={handleUpdateNote}
handleDeleteNote={handleDeleteNote}
/>
))}
<AddNote handleAddNote={handleAddNote} />
</div>
);
};
export default NotesList;
Index.css
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
}
.container {
max-width: 960px;
margin-right: auto;
margin-left: auto;
padding-right: 15px;
padding-left: 15px;
min-height: 100vh;
}
.notes-list {
display: grid;
grid-gap: 1rem;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
}
.note.new {
background-color: peachpuff;
}
textarea {
border: none;
resize: none;
background-color: peachpuff;
}
textarea:focus {
outline: none;
}
.save {
background-color: peachpuff;
border: none;
border-radius: 15px;
padding: 5px 10px 5px 10px;
}
.save:hover {
background-color: lavender;
cursor: pointer;
}
.note {
background-color: lavender;
border-radius: 10px;
padding: 1rem;
min-height: 170px;
display: flex;
flex-direction: column;
justify-content: space-between;
white-space: pre-wrap;
}
.note-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
.delete-icon {
cursor: pointer;
}
.update-icon {
cursor: pointer;
position: static;
}
.search {
display: flex;
align-items: center;
background-color: rgb(233, 233, 233);
border-radius: 10px;
padding: 5px;
margin-bottom: 1.5em;
}
.search input {
border: none;
background-color: rgb(233, 233, 233);
width: 100%;
}
.search input:focus {
outline: none;
}
What I'm trying to do is establish an update button where a user can update their Note by clicking the update button. What am I missing that's not allowing me to establish an updateNote feature? When you click on the update button, it just highlights the text on the notes section to the right of the note you click. !![Text](
Probably you are missing id and date in your new object
const updatedNote = {
text: text,
id,
date: Date.now()
};
also probably not related to the problem but you are shadowing the notes variable, which is a bad practice
Can you remove the position static
.update-icon {
cursor: pointer;
position: static;
}
To
update-icon {
cursor: pointer;
}
Can someone help me to display specific song while clicking on that in Your Playlist container on the left??
I am trying to list data on the left in Your Playlist container When I click on one of the music it should show it in Your Playlist container. It has to save it to browser history as well and it has to remove it from Search because it is already gonna be in Your Playlist container. I will deploy it later to Firebase but now I need help.
It should be added to the left while clicking on one of the listed songs after a search.
Please support me on that.
I am adding my codes here as well for my project:
I have App.js
import "./App.css";
import MySongs from "./MySongs.js";
import Search from "./Search.jsx";
function App() {
return (
<div className="App">
<div className="body">
<MySongs />
<Search />
</div>
</div>
);
}
export default App;
.App {
background-color: #303030;
width: 100%;
height: 100vh;
}
.body {
display: flex;
}
I have Search.jsx
import React, { useState, useEffect, useRef } from "react";
import "./Search.css";
import styled from 'styled-components';
import { IoSearch,IoClose } from "react-icons/io5";
import {motion, AnimatePresence} from "framer-motion";
import {useClickOutside} from "react-click-outside-hook";
import MoonLoader from 'react-spinners/MoonLoader';
import { useDebounce } from "./hooks/debounceHook";
import axios from "axios";
import { TvShow } from "./tvShow";
const SearchBarContainer = styled(motion.div)`
margin-left: 10px;
margin-top: 20px;
display: flex;
flex-direction: column;
width: 96%;
height: 2.5em;
background-color: #424242;
border-radius: 3px;
`;
const SearchInputContainer = styled.div`
width: 98%;
min-height: 2.5em;
display: flex;
align-items: center;
position: relative;
padding: 2px 15px;
`;
const SearchInput = styled.input`
width: 100%;
height: 100%;
outline: none;
border: none;
font-size: 15px;
color: white;
font-weight: 300;
border-radius: 6px;
background-color: transparent;
&:focus {
outline: none;
&::placeholder {
opacity: 0;
}
}
&::placeholder {
color: #white;
transition: all 250ms ease-in-out;
}
`;
const SearchIcon = styled.span`
color: #bebebe;
font-size: 14px;
margin-right: 10px;
margin-top: 6px;
vertical-align: middle;
`;
const CloseIcon = styled(motion.span)`
color: #bebebe;
font-size: 15px;
vertical-align: middle;
transition: all 200ms ease-in-out;
cursor: pointer;
&:hover {
color: #dfdfdf;
}
`;
const LineSeperator = styled.span`
display: flex;
min-width: 100%;
min-height: 2px;
background-color: #d8d8d878;
`;
const SearchContent = styled.div`
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
padding: 1em;
overflow-y: auto;
`;
const LoadingWrapper = styled.div`
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
`;
const WarningMessage = styled.span`
color: #a1a1a1;
font-size: 14px;
display: flex;
align-self: center;
justify-self: center;
`;
const containerVariants = {
expanded: {
height: "26em",
},
collapsed: {
height: "2.5em",
},
};
const containerTransition = { type: "spring", damping: 22, stiffness: 150 };
export function SearchBar(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [tvShows, setTvShows] = useState([]);
const [noTvShows, setNoTvShows] = useState(false);
const isEmpty = !tvShows || tvShows.length === 0;
const changeHandler = (e) => {
e.preventDefault();
if (e.target.value.trim() === "") setNoTvShows(false);
setSearchQuery(e.target.value);
};
const expandContainer = () => {
setExpanded(true);
};
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoTvShows(false);
setTvShows([]);
if (inputRef.current) inputRef.current.value = "";
};
useEffect(() => {
if (isClickedOutside) collapseContainer();
}, [isClickedOutside]);
const searchTvShow = async () => {
if (!searchQuery || searchQuery.trim() === "") return;
setLoading(true);
setNoTvShows(false);
const options = {
method: 'GET',
url: 'https://deezerdevs-deezer.p.rapidapi.com/search',
params: {q: searchQuery},
headers: {
'x-rapidapi-host': 'deezerdevs-deezer.p.rapidapi.com',
'x-rapidapi-key': '6a99d5e101msh1e9f2b2f948746fp1ae1f3jsn6b458fe8b4e4'
}
};
axios.request(options).then(function (response) {
if (response) {
if (response.data && response.data.length === 0) setNoTvShows(true);
setTvShows(response.data.data);
}
}).catch(function (error) {
console.error(error);
});
setLoading(false);
};
useDebounce(searchQuery, 500, searchTvShow);
// console.log(tvShows);
return (
<div className="my__search">
<SearchBarContainer
animate={isExpanded ? "expanded" : "collapsed"}
variants={containerVariants}
transition={containerTransition}
ref={parentRef}
>
<SearchInputContainer>
<SearchIcon>
<IoSearch />
</SearchIcon>
<SearchInput
placeholder="Search for Series/Shows"
onFocus={expandContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (
<CloseIcon
key="close-icon"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={collapseContainer}
transition={{ duration: 0.2 }}
>
<IoClose />
</CloseIcon>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator />}
{isExpanded && (
<SearchContent>
{isLoading && (
<LoadingWrapper>
<MoonLoader loading color="#000" size={20} />
</LoadingWrapper>
)}
{!isLoading && isEmpty && !noTvShows && (
<LoadingWrapper>
<WarningMessage>Start typing to Search</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && noTvShows && (
<LoadingWrapper>
<WarningMessage>No Tv Shows or Series found!</WarningMessage>
</LoadingWrapper>
)}
{!isLoading && !isEmpty && (
<>
{tvShows.map((show) => (
<TvShow
key={show.id}
thumbnailSrc={show.album.cover_medium}
name={show.title_short}
artist={show.artist.name}
/>
))}
</>
)}
</SearchContent>
)}
</SearchBarContainer>
</div>
);
}
export default SearchBar;
.my__search {
margin-top: 20px;
flex: 0.6;
height: 450px;
border-radius: 5px;
border: 1px solid black;
margin-left: 80px;
background-color: #424242;
}
I have tvShow.jsx
import React, { useState } from "react";
import styled from "styled-components";
import {ImDownload} from "react-icons/im";
const TvShowContainer = styled.div`
width: 96%%;
min-height: 3em;
display: flex;
border-bottom: 2px solid #555555;
align-items: center;
`;
const Thumbnail = styled.div`
width: auto;
height: 80%;
display: flex;
flex: 0.4;
img {
border-radius: 20px;
width: auto;
height: 100%;
}
`;
const Name = styled.h3`
font-size: 12px;
color: white;
flex: 2;
display: flex;
flex-direction: column;
`;
const Artist = styled.span`
margin-top: 10px;
font-size: 8px;
color: white;
display: flex;
align-items: center;
`;
const Rating = styled.span`
color: #a1a1a1;
font-size: 16px;
display: flex;
flex: 0.2;
`;
export function TvShow(props) {
const { thumbnailSrc, name, artist,clickedMusic } = props;
const [wantedMusic, setWantedMusic] = useState("");
// const [clickedShow, setClickedShow] = useState("");
// function clickedContainer(e){
// const element = e.currentTarget();
// setClickedShow(element);
// console.log("I am clickedShow " +clickedShow);
// }
return (
<TvShowContainer onclick="location.href='#';" >
<Thumbnail>
<img src={thumbnailSrc} />
</Thumbnail>
<Name>{name}
<Artist>
{artist}
</Artist>
</Name>
</TvShowContainer>
);
}
I have mySongs.js
import React from "react";
import "./MySongs.css";
function MySongs() {
return (
<div className="my__songs">
<p>Your Playlist</p>
</div>
);
}
export default MySongs;
.my__songs {
margin-left: 10px;
margin-top: 20px;
flex: 0.3;
height: 300px;
height: 450px;
border: 1px solid black;
border-radius: 5px;
background-color: #424242;
}
.my__songs > p {
color: white;
opacity: 90%;
margin-left: 10px;
font-size: 13px;
}
Only partly answering the question: move an item from one list to another (and back) on mouse click.
The basic situation can be solved if you use the parent component to hold the state that the children components display. Then you only need to implement a function that toggles a "flag" (like selected), and the components can be rendered based on that flag.
const {useState} = React
const tracklist = [
{
id: 1,
title: 'Track 1',
selected: false,
},
{
id: 2,
title: 'Track 2',
selected: false,
},
{
id: 3,
title: 'Track 3',
selected: false,
},
{
id: 4,
title: 'Track 4',
selected: false,
},
{
id: 5,
title: 'Track 5',
selected: false,
},
]
const ListItem = ({ title, onToggleSelect }) => <div className="list-item" onClick={onToggleSelect}>{title}</div>
const App = ({ tracklist }) => {
const [tracks, setTracks] = useState(tracklist)
const toggleSelect = (id) => {
setTracks((prevState) => prevState.map(item => item.id === id ? {...item, selected: !item.selected} : item))
}
const listItem = (track) => <ListItem key={track.id} {...track} onToggleSelect={() => toggleSelect(track.id)}/>
return (
<div className="container" >
<div className="tracklist">
{
tracks
.filter(({ selected }) => selected)
.map(listItem)
}
</div>
<div className="tracklist">
{
tracks
.filter(({ selected }) => !selected)
.map(listItem)
}
</div>
</div>
)
}
ReactDOM.render(
<App tracklist={tracklist} />,
document.getElementById('root')
);
html, body {
margin: 0;
padding: 5px 10px;
}
.container {
display: grid;
grid-template-columns: 1fr 3fr;
gap: 20px;
}
.list-item {
cursor: pointer;
}
.list-item:hover {
background: lightgray;
}
.tracklist {
border: 1px solid gray;
}
<script src="https://unpkg.com/react#17/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#17/umd/react-dom.development.js" crossorigin></script>
<div id="root"></div>
I have a multi-tier, multi-column menu that has been built using https://github.com/kontentino/react-multilevel-dropdown. The problem I am having is that the submenu items keep appearing underneath the main items. I have tried multiple attempts using z-index but nothing seems to be helping.
I am including my menu component, my CSS for this page as well as an image of the problem.
If possible I want to avoid trying to roll a custom menu using lists, but am willing to entertain another multi-level menu package.
MenuBarComponent.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import Col from 'react-bootstrap/Col';
import Row from 'react-bootstrap/Row';
import Container from 'react-bootstrap/Container';
import { NavLink } from 'react-router-dom';
// import { CurrentPage_Update } from '../../redux/ActionCreators';
import Dropdown from 'react-multilevel-dropdown';
import { Loading } from './LoadingComponent';
import '../../shared/styles/menu.css'
const mapStateToProps = (state) => {
return {
siteMap: state.siteMap
}
}
const mapDispatchToProps = (dispatch) => {
return {
// CurrentPage_Update: (page) => { dispatch(CurrentPage_Update(page)) }
}
}
class MenuBar extends Component {
constructor(props) {
super(props);
this.state = {
openCount: 1,
isOpen: false
}
}
drawLink = (item) => {
return (
<NavLink
to={'/page_' + item.pageId}
className={`menu menu-link`}
key={"DDNavLink_" + item.pageId}
onClick={() => this.setState({ openCount: -1 })}
>{item.title}</NavLink>
);
}
NavListItem = (item, level, roles) => {
}
dropdownItem = (item, level, roles) => {
let brk = true;
//TODO: remove next line for production
item.roles = "*," + item.roles; //Make sure page is shown in development
if ((item.roles.indexOf("*") < 0) && (item.roles.indexOf("*") < 0)) {
var itemRoles = item.roles.toLowerCase().split(',');
for (var i = 0; i < itemRoles.length; i++) {
if (roles.indexOf(itemRoles[i]) > -1) {
//if found
brk = false;
break;
}
}
if (brk) return;
}
if (item.children.length > 0) {
//if any visible children show dropdown
//only endpoints are clickable
if (item.children.find((child) => { return child.visible !== "false" })) {
return (
<Dropdown.Item key={'Dropdown_' + item.pageId} className={`menu menu-item-container`}>
AA{item.title}
<Dropdown.Submenu position='right' className={`menu-UncontrolledDropdown sub${level + 1}`} >
{item.children.map((listItem) => {
return (this.dropdownItem(listItem, level + 1, roles))
})
}
</Dropdown.Submenu>
</Dropdown.Item>
)
}
else { //otherwise if no visible children show only the item
if (item.visible !== "false") {
return (
<Dropdown className={`sub${level + 1} menu menu-item-container`
} key={"DDItem_" + item.pageId} >
BB{level} { this.drawLink(item)}
</Dropdown >
)
}
}
}
else { //if no children show only item
return (
<Dropdown.Item className={`sub${level} menu menu-item-container`} key={"DDItem" + item.pageId}>
CC{level}
{this.drawLink(item, level)}
</Dropdown.Item>
)
}
}
render() {
// const setIsOpen = (value) => { this.setState({ isOpen: value }) }
// const toggle = () => { setIsOpen(!this.state.isOpen) };
if (this.props.siteMap.errMess) {
return (
<div>
{this.props.siteMap.errMess}
</div>
)
}
else if (this.props.siteMap.isLoading) {
return <Loading title="Site Menu" />
}
else {
var roles = this.props.siteMap.siteMapData.userRoles.toLowerCase();
var siteMap = this.props.siteMap.siteMapData.siteMap;
return (
<Container>
<Row>
<Col className="col-2 p-3 menu">
<NavLink to='/'>
<div className="align-top met-logo"></div>
</NavLink>
</Col>
<Col md={{ span: 9, offset: 1 }} className=" menu ">
{
siteMap.map((link, index) => {
return <div
key={index}
className='p-1 column menu menu-item-container menu-head'>
{this.dropdownItem(link, 0, roles)}
</div>
})
}
{/* https://github.com/kontentino/react-multilevel-dropdown#readme */}
</Col>
</Row>
</Container>
);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(MenuBar);
menu.css
/*Necessary to force dropdown items into a straight line */
nav>ul>li>a+div>ul>li>a {
text-indent: 15px;
/* Can use padding-left as well */
}
.met-logo {
background-image: url('../images/METLogo.jpg');
background-size: 100%;
background-repeat: no-repeat;
/* width: 128px;
height: 91px; */
width: 100px;
height: 65px;
/* border: 1px solid black; */
top: -5px;
padding: 3px;
}
.menu {
color: #000;
position: sticky;
top: 5px;
/* z-index: 1000000; */
/* border:solid 1px black; */
}
.menu-collapse {
position: absolute;
top: 10px;
}
.menu-dropdown-toggle {
color: #000;
}
.menu-item-container {
color: #000;
background-color: #ccc;
border: solid 1px #555;
white-space: pre-wrap;
}
.menu-item-container :hover {
background-color: #aaa;
}
.menu-link {
color: #000;
cursor: pointer;
}
.menu-bar {
color: #000;
}
/* .menu-toggler {
float: right;
margin-right: 10px;
} */
.menu-dropdown-container {
color: #000;
padding-top: 0px;
padding-bottom: 0px;
margin-top: 15px;
overflow: wrap;
}
.menu-dropdown-container ul {
box-sizing: content-box;
background-color: #fff !important;
border: 0px;
overflow: wrap;
}
.menu-link {
color: #000;
}
.menu-dropdown-title {
color: #000;
}
.menu-dropdown-body {
color: #000;
}
.menu-head {
z-index: 9000!important;
}
.menu-UncontrolledDropdown {
color: #000;
background-color: #fff!important;
border: solid 1px #555;
transform: translate(-10%, 15px);
/* z-index: 99999!important; */
/* position: absolute; */
}
.sub0 {
z-index: 12000;
}
.sub1 {
z-index: 13000;
}
.sub2 {
z-index: 14000;
}
.sub3 {
z-index: 15000;
}
.sub4 {
z-index: 16000;
}
.menu-UncontrolledDropdown :hover {
background-color: #bbf;
}
.menu-UncontrolledDropdown>div {
background-color: #fff;
}
/* .fullWidth {
width: 100vw!important;
margin: 2px 2px 2px 2px;
}*/
.column {
display: inline-block;
padding: 8px;
font: 20px;
font-weight: 500;
}
----------
And here is the example of what the problem is.
I am not using JQuery in this project but React-Bootstrap is used.
After going through the code I finally removed all positioning and z-index from my CSS sheet, leaving in the translates for correct positioning. Leaving the implementation to the component code rather than my own has solved the problems including overlay on the menu column next to the dropdown.
I am using redux form for form related task. I am using react-dropzone for uploading files and redux form is used along with it. I could submit file to server but could not show that submitted file as initialValues for upload field.
here is how i have done
const Form = ({ item }) => {
return (
<>
<form onSubmit={handleSubmit(val => handleItemSubmit(val, mutation))}>
<Field name="photo" component={UploadField} />
</form>
</>
)
}
export default compose(
connect((state, props) => {
const { items } = props;
return {
initialValues: {
...items,
photo:
items && items.photoPath
? { name: items.photoName, size: 12345 }
: null,
languages:
items &&
items.languages &&
get(items, "languages", "")
.split(",")
.map(lang => ({
label: lang,
value: lang
}))
}
};
}),
reduxForm({
form: "item_form",
enableReinitialize: true,
fields: { ...requiredFields },
validate
})
)(Form);
import React from "react";
import styled from "styled-components";
import { useDropzone } from "react-dropzone";
export const UploadField = props => {
const {
input: { onChange },
disabled
} = props;
const { getRootProps, getInputProps } = useDropzone({
onDrop: files => onChange(files)
});
const files = props.input.value;
if (disabled) {
return null;
}
return (
<>
<DropzoneContainer {...getRootProps()}>
<input {...getInputProps()} />
Upload files here!
</DropzoneContainer>
<br />
<div>
{Object.keys(files).map((key, index) => {
console.log("FILES ---", files);
return (
<ThumbsContainer>
<p>{files[key].name}</p>
</ThumbsContainer>
);
})}
</div>
</>
);
};
export default UploadField;
const DropzoneContainer = styled.div`
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
border-width: 2px;
border-radius: 2px;
border-color: ${props => getColor(props)};
border-style: dashed;
background-color: #fafafa;
color: #bdbdbd;
outline: none;
transition: border 0.24s ease-in-out;
`,
ThumbsContainer = styled.aside`
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-top: 16px;
`,
Thumb = styled.div`
display: inline-flex;
border-radius: 2px;
border: solid 1px #eaeaea;
margin-bottom: 8px;
width: 100;
height: 100;
padding: 4px;
box-sizing: border-box;
`,
ThumbInner = styled.div`
display: flex;
min-width: 0;
overflow: hidden;
`,
Image = styled.img`
display: block;
max-width: 100%;
width: 180px;
height: 180px;
`;
const getColor = props => {
if (props.isDragAccept) {
return "#00e676";
}
if (props.isDragReject) {
return "#ff1744";
}
if (props.isDragActive) {
return "#2196f3";
}
return "#eeeeee";
};
I get these two fields when uploading
other gets pre filled but not to upload field in my case.