// Custom Input component
const Container = styled.div``;
const InputBase = styled.div<{ width: string }>`
display: flex;
width: ${(props) => props.width};
height: 42px;
justify-content: space-between;
align-items: center;
padding: 9px 10px;
border-radius: 8px;
border: solid 2px ${color.gray[200]};
background-color: white;
&[data-focus="true"] {
border: solid 2px ${color.gray[300]};
}
&[data-disabled="true"] {
background-color: ${color.background.original};
}
`;
const InputArea = styled.input`
width: 100%;
border: 0;
background-color: inherit;
font-size: 15px;
font-weight: normal;
&:focus {
outline: none;
}
&:disabled {
color: ${color.gray[600]};
}
::placeholder {
color: ${color.gray[600]};
}
`;
const Label = styled.label`
color: ${color.gray[600]};
font-size: 15px;
font-weight: bold;
display: inline-block;
margin-bottom: 10px;
`;
const Input: ForwardRefRenderFunction<HTMLInputElement, Props> = (
{ formWidth = "100%", icon = null, labelText, onFocus, onBlur, ...props },
ref
) => {
const [isFocus, setIsFocus] = useState<boolean>(false);
const handelFocus = (e) => {
setIsFocus(true);
if (onFocus) onFocus(e);
};
const handleBlur = (e) => {
setIsFocus(false);
if (onBlur) onBlur(e);
};
return (
<Container>
{labelText && <Label htmlFor={props.id}>{labelText}</Label>}
<InputBase
data-focus={isFocus}
data-disabled={props.disabled}
width={formWidth}
>
<InputArea
ref={ref}
type="text"
{...props}
onFocus={handelFocus}
onBlur={handleBlur}
/>
{icon}
</InputBase>
</Container>
);
};
export default forwardRef(Input);
I'm making the component into a library and using it in another project.
The code below works normally when testing in the library.
describe("Input", () => {
it("test", async () => {
const handleBlur = jest.fn();
render(
<Input data-testid="input" onBlur={handleBlur} />
);
const input = screen.getByTestId("input") as HTMLInputElement;
fireEvent.change(input, { target: { value: "test" } });
fireEvent.blur(input);
expect(handleBlur).toHaveBeenCalledTimes(1);
});
});
However, in other projects that use that library, fireEvent.blur(input) does not work (toHaveBeenCalledTime is 0) and fireEvent.focusOut(input) does.
Is there something wrong with the library bundling process? Or is there something I'm missing?
Related
I am having an issue with my application's state tracking. I have an edit form, and when I click on a ticket to set it as the selected ticket, I pass the selected ticket down to the edit form. However, I think I have an issue with the way my state is being tracked.
I believe the exact issue is here:
function handleClick(ticket) {
setSelectedTicket(ticket);
}
If I edit a ticket then click on any other tickets, the updated info then gets passed to all the other tickets.
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import TicketDetails from "./TicketDetails";
const Container = styled.div`
position: absolute;
left: 50%;
top: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
background-color: rgba(255, 255, 255, 0.8);
border-radius: 0.25em;
box-shadow: 0 0 0.25em rgba(0, 0, 0, 0.25);
box-sizing: border-box;
left: 50%;
width: 50vw;
height: 50vh;
position: fixed;
top: 50%;
transform: translate(-50%, -50%);
font-size: 1.5rem;
`;
const Gradient = styled.div`
background: black;
height: 2px;
margin: 1rem;
width: 96%;
`;
const H2 = styled.h2`
margin: 1rem;
padding: 0;
text-align: center;
width: 100%;
`;
const TicketContainer = styled.div`
display: flex;
flex-direction: row;
`;
const TicketList = styled.div`
display: flex;
margin-left: 1rem;
flex-direction: column;
height: 38vh;
overflow-y: auto;
width: 50%;
`;
const Ticket = styled.div`
margin-right: 2rem;
font-size: 1.3rem;
cursor: pointer;
`;
const DetailsContainer = styled.div`
align-items: center;
text-align: center;
margin: 5px auto auto auto;
width: 300px;
`;
const MyTickets = ({ user }) => {
const [tickets, setTickets] = useState([]);
const [selectedTicket, setSelectedTicket] = useState(null);
useEffect(() => {
fetch(`/users/${user.id}`)
.then((r) => r.json())
.then((data) => {
if (data.tickets.length > 25) {
setTickets(data.tickets.slice(0, 25));
} else {
setTickets(data.tickets);
}
});
}, []);
function refreshTickets() {
fetch(`/users/${user.id}`)
.then((r) => r.json())
.then((data) => {
if (data.tickets.length > 25) {
setTickets(data.tickets.slice(0, 25));
} else {
setTickets(data.tickets);
}
});
}
function limitChars(string) {
if (string.length > 18) {
return string.slice(0, 18) + "...";
}
return string;
}
function handleClick(ticket) {
setSelectedTicket(ticket);
}
return (
<>
<div className="bg"></div>
<div className="bg bg2"></div>
<div className="bg bg3"></div>
<Container>
<H2>{user.username}'s tickets</H2>
<Gradient></Gradient>
<TicketContainer>
<TicketList>
{tickets.map((ticket, index) => {
return (
<Ticket key={index} onClick={() => handleClick(ticket)}>
{index + 1}: {limitChars(ticket.title)}
</Ticket>
);
})}
</TicketList>
<DetailsContainer>
{selectedTicket ? (
<TicketDetails
ticket={selectedTicket}
refreshTickets={refreshTickets}
/>
) : (
<div>Select a ticket</div>
)}
</DetailsContainer>
</TicketContainer>
</Container>
</>
);
};
export default MyTickets;
Below is how I update the ticket:
import React, { useState } from "react";
import styled from "styled-components";
const Container = styled.div`
user-select: none;
border-radius: 2px;
border: 2px solid transparent;
box-shadow: none;
box-sizing: border-box;
padding: 8px;
margin-bottom: 8px;
`;
const Title = styled.div`
font-size: 1.5rem;
overflow-wrap: break-word;
margin: 0.5rem;
`;
const Gradient = styled.div`
background: black;
height: 2px;
margin: 0.5rem;
`;
const Description = styled.div`
font-size: 1rem;
overflow-wrap: break-word;
word-break: break-all;
margin: 0.5rem;
`;
const ContentContainer = styled.div`
-webkit-box-flex: 1;
flex-grow: 1;
flex-basis: 100%;
display: flex;
flex-direction: column;
align-items: center;
`;
const Input = styled.input`
height: 2rem;
width: 100%;
text-align: center;
`;
const TextArea = styled.textarea`
height: 100px;
width: 98%;
padding: 1%;
border: none;
resize: none;
`;
const ButtonContainer = styled.div`
flex-direction: row;
`;
const EditButton = styled.button`
width: 5rem;
`;
const categories = [
"URGENT",
"Meetings",
"To Do",
"In Progress",
"Needs Review",
];
const Category = styled.div`
margin: 0.5rem;
`;
const TicketDetails = ({ ticket, refreshTickets }) => {
const [edit, setEdit] = useState(false);
const [title, setTitle] = useState(ticket.title);
const [initialTitle, setInitialTitle] = useState(ticket.title);
const [description, setDescription] = useState(ticket.description);
const [descriptionInit, setDescriptionInit] = useState(ticket.description);
const handleSubmit = (e) => {
e.preventDefault();
fetch(`/tickets/${ticket.id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: title, description: description }),
})
.then((r) => r.json())
.then((d) => {
console.log("updated ticket", d);
setTitle(d.title);
setDescription(d.description);
refreshTickets();
});
setEdit(false);
};
const handleReset = (e) => {
setTitle(initialTitle);
setDescription(descriptionInit);
};
const handleCancel = (e) => {
setTitle(initialTitle);
setDescription(descriptionInit);
setEdit(false);
};
return (
<>
<Category>{categories[ticket.category_id - 1]}</Category>
<Gradient></Gradient>
{edit ? (
<Container
style={{
backgroundColor: "#B1D4E0",
}}
>
<form onSubmit={handleSubmit} onReset={handleReset}>
<Input
type="text"
id="title"
autoComplete="off"
value={title}
onChange={(e) => setTitle(e.target.value)}
/>
<Gradient></Gradient>
<TextArea
type="text"
id="description"
autoComplete="off"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
<input type="submit" value="Submit" />
<input type="reset" value="Reset" />
<button onClick={handleCancel}>Cancel</button>
</form>
</Container>
) : (
<Container
style={{
backgroundColor: "#B1D4E0",
}}
>
<Title>{ticket.title}</Title>
<Gradient></Gradient>
<ContentContainer>
<Description>{ticket.description}</Description>
</ContentContainer>
<ButtonContainer>
<EditButton onClick={() => setEdit(true)}>Edit</EditButton>
</ButtonContainer>
</Container>
)}
</>
);
};
export default TicketDetails;
I think that the info updated is kept because the component is always mount, you change the ticket value in props but this does not re-initialize your state in TicketDetails. To update the values when other ticket is selected add a useEffect like this:
const TicketDetails = ({ ticket, refreshTickets }) => {
//...
useEffect(() => {
setTitle(ticket.title);
setInitialTitle(ticket.title);
setDescription(ticket.description);
setDescriptionInit(ticket.description);
}, [ticket]);
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 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.
working on a React project currently, using webpack, webpack-dev-server, hot module reloading, React Router, styled-components etc
I have created a Table component where I try to dynamically determine the number of rows to render within the Table based on the height of the parent. Within Chrome, this works as expected, but in Firefox I am finding that all my rows are being rendered and therefore are not bound within the parent component.
Is this a known issue or are there any suggested workarounds or is there something horrendously wrong with my code?
Parent component (App):
const MainContainer = styled.div`
background-color: ${colours.white};
border-radius: 4px;
box-shadow: 0 0 9px 0 #dedede;
display: flex;
flex-wrap: wrap;
height: 85%;
width: 92.5%;
`;
const InnerContainer = styled.div`
display: flex;
flex-direction: column;
width: calc(100% - 9.375em);
`;
const SearchAndCountPlaceholder = styled.div`
height: 12.5%;
`;
const SidebarPlaceholder = styled.div`
background-color: ${colours.blue.light};
height: 100%;
opacity: 0.12;
width: 9.375em;
`;
const LoadMoreButtonContainerPlaceholder = styled.div`
height: 15%;
`;
const App = () => (
<MainContainer className="app-component">
<SidebarPlaceholder />
<InnerContainer>
<SearchAndCountPlaceholder />
<Table tableHeadings={tableHeadings} masterData={mockData} />
<LoadMoreButtonContainerPlaceholder />
</InnerContainer>
</MainContainer>
);
Table component:
const Container = styled.div`
background-color: ${colours.white};
height: 100%;
overflow-x: scroll;
padding-bottom: 1em;
width: 100%;
`;
const StyledTable = styled.table`
border-bottom: 2px solid ${colours.grey.lighter};
border-collapse: collapse;
margin: 1.25em;
table-layout: fixed;
width: 100%;
& thead {
border-bottom: inherit;
color: ${colours.grey.lighter};
font-size: 0.75em;
font-weight: 700;
line-height: 1em;
text-align: left;
text-transform: uppercase;
& th {
padding: 0.75em 1em 0.75em 1em;
width: 7.25em;
}
& th:not(.Source) {
cursor: pointer;
}
& span {
color: ${colours.blue.dark};
font-size: 1em;
font-weight: 300;
margin-left: 0.313em;
}
}
& tbody {
color: ${colours.grey.dark};
font-size: 0.813em;
line-height: 1.125em;
& tr {
border-bottom: 1px solid ${colours.grey.lightest};
& td {
padding: 1em;
}
}
& .masterData {
font-weight: 700;
}
}
`;
let numberOfRowsToDisplay;
const calculateNumberOfRowsToDisplay = () => {
const tableHeight = document.querySelector('.table-component').offsetHeight;
const rowHeight = 40; // height of row in pixels
const numberOfRowsNotToIncludeInCalculation = 2; // header & scrollbar
numberOfRowsToDisplay = Math.floor(
tableHeight / rowHeight - numberOfRowsNotToIncludeInCalculation
);
};
class Table extends Component {
constructor(props) {
super(props);
this.state = { columnToSort: '' };
this.onColumnSortClick = this.onColumnSortClick.bind(this);
}
componentDidMount() {
calculateNumberOfRowsToDisplay();
}
onColumnSortClick(event) {
event.preventDefault();
const columnToSort = event.target.className;
this.setState(prevState => {
if (prevState.columnToSort === columnToSort) {
return { columnToSort: '' };
}
return { columnToSort };
});
}
render() {
const { tableHeadings, masterData } = this.props;
const { columnToSort } = this.state;
const upArrow = '⬆';
const downArrow = '⬇';
return (
<Container className="table-component">
<StyledTable>
<thead>
<tr>
{tableHeadings.map(heading => (
<th
className={heading}
key={heading}
onClick={this.onColumnSortClick}
>
{heading}{' '}
{heading !== 'Source' ? (
<span>
{heading === columnToSort ? upArrow : downArrow}
</span>
) : null}
</th>
))}
</tr>
</thead>
<tbody>
{masterData &&
masterData.slice(0, numberOfRowsToDisplay).map(data => {
const dataKey = uuidv4();
return (
<tr className="masterData" key={dataKey}>
<td>Master</td>
{Object.values(data).map(datum => {
const datumKey = uuidv4();
return <td key={datumKey}>{datum}</td>;
})}
</tr>
);
})}
</tbody>
</StyledTable>
</Container>
);
}
}
Thanks in advance!
Maybe not the answer I was quite looking for, but in the end I have set the dynamic numberOfRowsToDisplay as part of the component's state; which has given me the UI result I was looking for