Create Button from each item in array React - javascript

I am working with Unsplash API and I am trying to get the images to download. I can get them to download, however it downloads every single photo instead of just the one I want when I use a for loop. If I remove the saveAs part outside of the loop it only downloads the final image in the array instead of the others no matter what button I click. Here is my code:
import React, { useState, useEffect } from 'react';
import { Heading } from './components/Heading';
import { Loader } from './components/Loader';
import { UnsplashImage } from './components/UnsplashImage';
import InfiniteScroll from 'react-infinite-scroll-component';
import { saveAs } from 'file-saver';
import axios from 'axios';
import styled from 'styled-components';
import { createGlobalStyle } from 'styled-components';
import SearchPhotos from './components/searchPhotos';
import Heart from './components/Heart';
import { FileUpload } from './components/Upload';
const GlobalStyle = createGlobalStyle`
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body{
font-family: sans-serif;
}
`;
const WrapperImg = styled.section`
max-width: 70rem;
margin: 4rem auto;
display: grid;
grid-gap: 1em;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
`;
const H1 = styled.h1`
max-width: 70rem;
margin: 4rem auto;
`;
const Div = styled.div`
display: flex;
flex-direction: column;
margin-bottom: 2rem;
height: auto;
width: 100%;
position: relative;
`;
function App() {
const [images, setImages] = useState([]);
useEffect(() => {
fetchImages();
}, [])
const fetchImages = () => {
const apiURL = "https://api.unsplash.com";
const apiKey = "MY_KEY_";
axios
.get(`${apiURL}/photos/random?client_id=${apiKey}&count=1`)
.then(res => setImages([...images, ...res.data]))
}
const imgURL = images.map((download) => {
//console.log(download.urls.full)
return download.urls.full;
});
const Download = () => {
const downloadImage = () => {
for (var i = 0; i < imgURL.length; i++) {
var red = imgURL[i];
//saveAs(red, 'image.jpg');
console.log(red);
}
//saveAs(red, 'image.jpg');
}
return <button onClick={downloadImage}>Download</button>
}
return (
<div className="App">
<Heading />
<GlobalStyle />
<SearchPhotos />
<InfiniteScroll
dataLength={images.length}
next={fetchImages}
hasMore={true}
loader={<Loader />}
>
<H1>Main Feed:</H1>
<WrapperImg>
<FileUpload />
{images.map(image =>
(<>
<Div>
<Heart />
<UnsplashImage url={image.urls.thumb} key={image.id} />
<p className="like"> Amount of Likes ❤️ {image.likes}</p>
<Download />
</Div>
</>))}
</WrapperImg>
</InfiniteScroll>
</div>
);
}
export default App;

Try this to download each image, I have removed loop and modified the Download function
const imgURL = images.map((download) => {
//console.log(download.urls.full)
return download.urls.full;
});
const downloadImage = (index) => {
var red = imgURL[index];
saveAs(red, 'image.jpg');
}
return (
<div className="App">
<WrapperImg>
{images.map((image,index) =>
(<>
<Div>
<UnsplashImage url={image.urls.thumb} key={image.id} />
<button onClick={()=> { downloadImage(index) }>Download</button>
</Div>
</>))}
</WrapperImg>
</div>
);
}

This should help you:
// the download buttons with specific links will all be stored in the array returned here
const allYourDownloadButtons = images.map((download) => {
let imgURL = download.urls.full;
// saveAs was not mentioned in your code, if it's in scope here, you can directly pass it
return <DownloadV2 imgURL={imgURL} saveAs={saveAs} />;
});
const DownloadV2 = ({ imgURL, saveAs }) => {
return <button onClick={() => saveAs(imgURL, 'image.jpg')}>Download</button>;
};
Once you display the buttons on the UI, clicking on them will pass the specific URL through saveAs.
In case you need to know how to use this, please share in the question where you were calling this button.
A very generic way to use it would be like this:
<div className="allMyDownloadButtons">
{allYourDownloadButtons}
</div>
Edit: based on your updates I can see that your job is even easier as you were already looping through the images:
<WrapperImg>
<FileUpload />
{images.map((image) => (
<>
<Div>
<Heart />
<UnsplashImage url={image.urls.thumb} key={image.id} />
<p className="like"> Amount of Likes ❤️ {image.likes}</p>
<DownloadV2 imgURL={image.urls.full} />
</Div>
</>
))}
</WrapperImg>

You have this question - firstly understand this What is the happening
You can open it in codepen vanilla Javascript or you can skip this. - enter link description here
const root = document.querySelector("#root");
const arr = [1,2,3,4,5];
arr.map(each => {
const newButton = document.createElement("button");
newButton.innerHTML = each;
newButton.addEventListener("click", () => {
console.log(each);
})
root.appendChild(newButton);
})
Now Come to your code :
<WrapperImg>
<FileUpload />
{images.map(image =>
(<>
<Div>
<Heart />
<UnsplashImage url={image.urls.thumb} key={image.id} />
<p className="like"> Amount of Likes ❤️ {image.likes}</p>
<Download downloadUrl={image.urls.full} />
</Div>
</>))}
</WrapperImg>
Now you can go to the Download Component and edit it.
const Download = ({downloadUrl}) => {
const downloadImage = () => {
saveAs(downloadUrl, 'image.jpg');
}
}
return <button onClick={downloadImage}>Download</button>
}
Here you don't need these code below
const imgURL = images.map((download) => {
//console.log(download.urls.full)
return download.urls.full;
});

Related

how to pin a note and sort it in an array

I was creating a notes app in react which has a pin functionality such that when I click on the pin icon on a particular note that particular note is displayed first. the user should only be able to pin 2 notes (and I've added that condition) the problem is The pin functionality is working perfectly fine but when I again click on a pinned note I want to un-pin it and again arrange it back in its place, how can i achieve this?
React code =
import React, { useState } from "react";
import "../styles/Notes.css";
import { useToast, Wrap, WrapItem, Button } from '#chakra-ui/react'
import { BsFillPinFill } from "react-icons/bs"
import { BsTrashFill } from "react-icons/bs"
import { BsPinAngle } from "react-icons/bs";
function NotesComponent() {
const [notes, setNotes] = useState([]);
const [title, setTitle] = useState("");
const [tagline, setTagline] = useState("");
const [body, setBody] = useState("");
const [page, setPage] = useState(1);
const toast = useToast()
// submit handler
const handleSubmit = (event) => {
event.preventDefault();
if (!title || !tagline || !body) {
toast({
title: 'Please complete the input',
status: 'error',
duration: 9000,
isClosable: true,
});
return;
}
// generating randome number to use as id
function generateUniqueNumber() {
let uniqueNumber = "";
while (uniqueNumber.length < 4) {
let digit = Math.floor(Math.random() * 10);
if (!uniqueNumber.includes(digit)) {
uniqueNumber += digit;
}
}
return uniqueNumber;
}
let number = generateUniqueNumber();
setNotes([...notes, { title, tagline, body, pinned: false, id: number }]);
setTitle("");
setTagline("");
setBody("");
};
// executing on click on the pen icon
const togglePin = (index) => {
setNotes(
notes.map((note, i) => {
if (i === index) {
let newNote = { ...note };
newNote.pinned = !note.pinned;
return newNote;
}
return note;
})
);
};
// sorting it
const sortedNotes = notes.sort((a, b) => {
if (a.pinned === b.pinned) {
return 0;
}
return a.pinned ? -1 : 1;
})
.map((note, i) => {
let newNote = { ...note };
if (note.pinned) {
const pinnedCount = notes.filter((n) => n.pinned).length;
if (pinnedCount > 2) {
newNote.pinned = false;
}
}
return newNote;
});
const pages = [1, 2, 3, 4, 5, 6];
const pageChnageHandler = (e) => {
setPage(e.target.innerText);
};
const deleteHandler = (id) => {
let index = id
const newArrayAfterDeleting = notes.filter((item) => item.id !== index)
setNotes(newArrayAfterDeleting)
}
return (
<div className="notes-app-container">
<form onSubmit={handleSubmit} className="notes-form">
<input
type="text"
placeholder="Title"
value={title}
onChange={(event) => setTitle(event.target.value)}
className="notes-input"
/>
<input
type="text"
placeholder="Tagline"
value={tagline}
onChange={(event) => setTagline(event.target.value)}
className="notes-input"
/>
<textarea
placeholder="Body"
value={body}
onChange={(event) => setBody(event.target.value)}
className="notes-textarea"
/>
<button type="submit" className="notes-button">
Add Note
</button>
</form>
<div className="enteredNotesMainParent">
{sortedNotes.slice(page * 6 - 6, page * 6).map((note, i) => (
<div key={i} className="enteredNoteIndivitual">
<div>{note.title}</div>
<div>{note.tagline}</div>
<div>{note.body}</div>
<br />
<div className="noteCtaHold">
<div>
<BsFillPinFill className="noteIcon" onClick={() => togglePin(i)} />
</div>
<div>
<BsTrashFill className="noteIcon" onClick={() => deleteHandler(note.id)} />
</div>
</div>
</div>
))}
</div>
{notes.length === 0 ? <p> Add some notes✅ </p> : ""}
{notes.length >= 4 && <div className="pagesHold">
{pages.map((item) => {
return <p onClick={pageChnageHandler} className="indivitualPage"> {item} </p>
})}
</div>}
</div>
);
}
export default NotesComponent;
can somebody please help me achieve this that if a note is pinned and if I click on that pinned note it should get un-pinned and re arrange back
You don't necessarily have to use sort() to get the pinned notes on top.
Just render the list twice: once for the pinned notes, filtering out the unpinned ones, and again for the rest of the list, filtering the pinned ones.
This way you don't have to concern yourself with where a given note is within the original list, because the original list doesn't change.
// creates a list of sample notes; not relevant to the funcionality.
const notes = Array.from({length: 6}, (_, i) => ({
title: `Note ${i + 1}`,
id: i
}))
function Notes ({notes}) {
// keep a list of the pinned note ids
const [pinned, setPinned] = React.useState([]);
// filter to get separate lists of pinned and unpinned notes
const pinnedNotes = notes.filter(({ id }) => pinned.includes(id));
const unpinnedNotes = notes.filter(({ id }) => !pinned.includes(id));
// to pin a note: add its id to the pinned list
const pin = id => setPinned([...pinned, id]);
// to unpin a note: remove its id from the pinned list
const unpin = id => {
pinned.splice(pinned.indexOf(id), 1);
setPinned([...pinned]);
}
// render both lists
return (
<div className="container">
<ul className="pinned">
{ pinnedNotes.map(note => (
<li key={note.id} onClick={() => unpin(note.id)}>{note.title}</li>
))}
</ul>
<ul className="unpinned">
{ unpinnedNotes.map(note => (
<li key={note.id} onClick={() => pin(note.id)}>{note.title}</li>
))}
</ul>
</div>
)
}
const root = ReactDOM.render(<Notes notes={notes} />, document.getElementById('root'));
/* all cosmetic. not necessary for it to work. */
.container {
font-family: sans-serif;
font-size: 12px;
}
.pinned {
background: skyblue;
margin-bottom: 1rem;
display: flex;
flex-wrap: wrap;
gap: 0.5em;
padding: 0 0.5em;
}
.pinned:empty::after {
content: "No pinned items. Click a note below to pin it.";
padding: 1rem;
display: block;
text-align: center;
}
.unpinned:empty::after {
content: "No unpinned notes.";
padding: 1rem;
display: block;
text-align: center;
}
.pinned li {
background: aliceblue;
}
.unpinned {
background: aliceblue;
}
li {
margin: 0.5em 0;
padding: 1em;
border: 1px solid steelblue;
border-radius: 2px;
}
ul {
list-style: none;
margin: 0;
padding: 0.25em;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React play different audio files at once - working with different refs

I'm creating a small app that plays an audio file and have some functionalities (loop, stop, mute). My goal is to add some more audio files that all should be played and stopped at once (one button to control all), but each will have a mute button, and I'm not sure what is the best practice to do so. I used useRef and thought maybe I need to set a refs array but how will I be able to start/stop them all at once, but still have the ability to control the mute separately?
This is my code so far. I guess I should split and have a different component for the audio sounds. Thanks for helping!
import React, {useState, useRef, useEffect} from 'react'
import {ImPlay2} from "react-icons/im"
import {ImStop} from "react-icons/im"
import styled from "styled-components"
import drums from '../loopfiles/DRUMS.mp3'
//import other audio files//
const AudioPlayer = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [isLooping, setIsLooping] = useState(false);
const [isOnMute, setIsOnMute] = useState(false);
const audioRef = useRef(new Audio(drums));
useEffect(() => {
if (isOnMute) {
audioRef.current.volume=0;
}
else {
audioRef.current.volume=1;
}
}, [isOnMute]);
useEffect(() => {
if (isPlaying) {
audioRef.current.play();
} else {
audioRef.current.pause();
audioRef.current.load();
}
}, [isPlaying]);
useEffect(() => {
if (isLooping) {
audioRef.current.loop = true;
} else {
audioRef.current.loop = false;
}
}, [isLooping]);
return (
<div>
{!isPlaying ? (
<button type="button"
className="play"
onClick={() => setIsPlaying(true)}>
<ImPlay2></ImPlay2> Play
</button>
) : (
<button type="button"
className="pause"
onClick={() => setIsPlaying(false)}>
<ImStop></ImStop> Stop
</button>
)}
<Flex>
<Switcher selected={isLooping} />
<Text
onClick={() => setIsLooping(true)}>
Loop
</Text>
<Text
onClick={() => setIsLooping(false)}>
Unloop
</Text>
</Flex>
<Flex>
<Switcher selected={isOnMute} />
<Text
onClick={() => setIsOnMute(true)}>
Mute
</Text>
<Text
onClick={() => setIsOnMute(false)}>
UnMute
</Text>
</Flex>
</div>
)
}
const Flex = styled.div`
margin-top: 5px;
display: flex;
align-items: center;
border-radius: 2px;
background: grey;
height: 20px;
width: 120px;
position: relative;
margin-bottom: 5px;
`;
const Switcher = styled.div`
background: black;
border-radius: 2px;
height: 20px;
line-height: 41px;
width: 50%;
cursor: pointer;
position: absolute;
transition: 0.5s;
-webkit-transition: 0.5s;
-moz-transition: 0.5s;
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.16);
z-index: 1;
left: ${({ selected }) =>
selected === true ? "0px" : "60px"};
`;
const Text = styled.div`
color: ${({ selected }) => (selected ? "black" : "white")};
font-size: 13px;
font-weight: 20;
line-height: 4px;
padding: 30;
width: 50%;
text-align: center;
cursor: pointer;
`;
export default AudioPlayer
If you would like to mute/unmute individual sounds, but play/pause all sounds together, then you will need to create a mute/unmute slider for each sound. I can think of a number of ways to do this. The "best choice" might depend upon the standards in the rest of the application, how many sounds you're importing, and whether they're likely to change.
Method 1: One way to do this would be creating one array containing isOnMute values for each sound and another array containing all refs, and then map(...) over each of the elements of the isOnMute array to create your sliders.
Method 2: Another way would be to have one array of objects containing all sounds, and then the ref and the isOnMute values could be stored within each object. You could map(...) over that to create your sliders as well.
Method 3: You could also create separate child components for each sound like you said, and then pass the mute property between the parent AudioPlayer and the child AudioChannel components.
Then anytime the play/pause button is clicked, you would need to update each of the refs in the array (via a forEach or each of the child components via toggling a single isPlaying property).
Regardless of which you choose, I also might like to recommend the use-sound npm package. It makes managing multiple sounds and their properties a little bit less cumbersome in my opinion, including the ability to play and pause with a single method call.
Here is a snippet for you/
Also do not forget to use according ids instead of idx and idx2
const AudioList = () => {
/* here populate the array in format: array of objects
{
drums: mp3file,
isPlaying: boolean,
setIsPlaying: boolean,
isLooping: boolean,
setIsLooping: boolean,
isOnMute: boolean,
setIsOnMute: boolean,
}[]
*/
const [audios, setAudios] = useState([
{ isPlaying: true, isOnMute: false, isLooping: true, drums: "Your mpr" },
]); // use initial audios
return (
<div>
<button
onClick={() => {
// similar to start all, mute all, you have full controll logic over all elements
// also you could implement add new audiofile, or delete, similar logic :)
setAudios((audios) =>
audios.map((audio) => ({ ...audio, isPlaying: false }))
);
}}
>
Stop all
</button>
<div>
{audios.map((audio, idx) => (
<AudioPlayer
key={idx}
{...audio}
setIsPlaying={(val) =>
setAudios((audios) =>
audios.map((audio, idx2) =>
idx === idx2 ? { ...audio, isPlaying: val } : audio
)
)
}
// similar for setMute and setLopping function,
// i think you can figure it out, it is just a snippet:)
/>
))}
</div>
</div>
);
};
const AudioPlayer = ({
drums,
isPlaying,
setIsPlaying,
isLooping,
setIsLooping,
isOnMute,
setIsOnMute,
}) => {
const audioRef = useRef(new Audio(drums));
// also you have full controll of element inside component
useEffect(() => {
if (isOnMute) {
audioRef.current.volume = 0;
} else {
audioRef.current.volume = 1;
}
}, [isOnMute]);
useEffect(() => {
if (isPlaying) {
audioRef.current.play();
} else {
audioRef.current.pause();
audioRef.current.load();
}
}, [isPlaying]);
useEffect(() => {
if (isLooping) {
audioRef.current.loop = true;
} else {
audioRef.current.loop = false;
}
}, [isLooping]);
return (
<div>
{!isPlaying ? (
<button
type="button"
className="play"
onClick={() => setIsPlaying(true)}
>
<ImPlay2></ImPlay2> Play
</button>
) : (
<button
type="button"
className="pause"
onClick={() => setIsPlaying(false)}
>
<ImStop></ImStop> Stop
</button>
)}
<Flex>
<Switcher selected={isLooping} />
<Text onClick={() => setIsLooping(true)}>Loop</Text>
<Text onClick={() => setIsLooping(false)}>Unloop</Text>
</Flex>
<Flex>
<Switcher selected={isOnMute} />
<Text onClick={() => setIsOnMute(true)}>Mute</Text>
<Text onClick={() => setIsOnMute(false)}>UnMute</Text>
</Flex>
</div>
);
};
I changed the following:
I added Audios.js containing:
const audios = () => {
return [
{
color: 'lightgreen',
isOnMute: false,
audio: drums,
title: 'Drums'
}, ...
AudioList.js:
const AudioList = ({isPlaying, isLooping}) => {
const [audioToPlay, setAudioToPlay] = useState();
useEffect(()=> {
setAudioToPlay(audios())
},[]) ....//and mapped through <AudioItem>:
AudioItem.js:
const AudioItem = ({audio, isPlaying, isLooping}) => {
const [isOnMute, setIsOnMute] = useState(false);
const audioRef = useRef(null);
useEffect(() => {
if (isLooping) {
audioRef.current.loop = true;
} else {.... //other functionality
added a progressBar.js:
const ProgressBar = ({isPlaying}) => {
const [completed, setCompleted] = useState({
count: 0
});const intervalId = useRef(null)
useEffect(() => {...
ControlPanel.js:
const ControlPanel = ({
setIsLooping, isLooping, isPlaying, setIsPlaying}) => {
return (
<div>
<PlayButton> //....
and Home.js containing controlpanel, AudioList, ProgressBar:
const Home = () => {
const [isPlaying, setIsPlaying] = useState(false);
const [isLooping, setIsLooping] = useState(false);
return (
<div>
<ControlPanel
setIsLooping={setIsLooping} //....

Is there a way to import a JS function inside a React component?

I've been trying to create a React HOC that will apply the corresponding styles to any component pass to it.
My idea was to do something similar to this
// Button.css.js
const styles = {backgroundColor:"blue"}
// Button.js
const Button = (props) => <button {...props}/>
// applyStyles.js
const applyStyles = (Component) => (props) => {
const styles = import style from `${Component.name}.css`
return <Component {...props} style={styles} />
}
I know applyStyles contains invalid syntax but is just to illustrate what is what I'm trying to do.
If any of you have a way around this I will really appreciate it.
You can try this
import (`/pathTofile/${Component.name}.css`)
.then(data => {
// rest of the code goes her
})
My recommendation is that you use styled-component for this:
const Wrapper = styled.div`
> * {
backgroundColor:"blue"
}`
function AppyStylesHOC(Component) {
return (props) => {
return (<Wrapper>
<Components {...props} />
</Wrapper>
})
}
You can try this:
Button.js
import React from 'react';
import styles from './Button.module.css';
const Button = props => {
return (
<button
className={styles.button}
type={props.type || 'button'}
onClick={props.onClick}
>
{props.children}
</button>
);
};
export default Button;
Button.module.css
.button {
font: inherit;
border: 1px solid #4f005f;
background: #4f005f;
color: white;
padding: 0.25rem 1rem;
cursor: pointer;
}
App.js
import Button from '../Button';
...
<Button type="submit">+ Add</Button>
...

Search field kicks me out on input field after 1 letter

this is in React. I have a search input field, however after typing one letter it keeps me out of the input field and renders the page again. The search field does work, it just kicks me out. I've tried adding a
onChange={(e) => setSearchField(e.target.value), function(e) {
e.preventDefault();
}}
to the input field but it doesn't work. Here's my whole file:
import React, { useState, useEffect } from "react";
import { Container, Row, Col, Input } from "reactstrap";
import MeetingTable from "./MeetingTable";
import MeetingCreate from "./MeetingCreate";
import MeetingEdit from "./MeetingEdit";
import APIURL from "../helpers/environment";
import styled from "styled-components";
import "./MeetingMain.css";
const MeetingMain = (props) => {
const Div = styled.div`
background-color: #363136;
opacity: 0.8;
border-radius: 5px;
padding-top: 10px;
padding-left: 10px;
`;
const [meetings, setMeetings] = useState([]);
const [updateActive, setUpdateActive] = useState(false);
const [meetingToUpdate, setMeetingToUpdate] = useState({});
const [searchField, setSearchField] = useState("");
const tableStyle = {
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
maxWidth: "1175px",
};
const fetchMeetings = () => {
fetch(`${APIURL}/meeting`, {
method: "GET",
headers: new Headers({
"Content-Type": "application/json",
Authorization: props.token,
}),
})
.then((res) => res.json())
.then((logData) => {
setMeetings(logData.meetings);
console.log(logData.meetings);
});
};
const editUpdateMeeting = (meeting) => {
setMeetingToUpdate(meeting);
console.log(meeting);
};
const updateOn = () => {
setUpdateActive(true);
};
const updateOff = () => {
setUpdateActive(false);
};
useEffect(() => {
fetchMeetings();
}, []);
const filteredMeetings = meetings.filter((meeting) =>
meeting.day.toLowerCase().includes(searchField.toLowerCase())
);
return (
<Div>
<Container style={tableStyle}>
<Row>
<Col md="12">
<MeetingCreate fetchMeetings={fetchMeetings} token={props.token} />
</Col>
<Col md="12">
<Input
className="search-field"
type="search"
placeholder="Search Meetings"
onChange={(e) => setSearchField(e.target.value)}
value={searchField}
/>
<MeetingTable
meetings={filteredMeetings}
editUpdateMeeting={editUpdateMeeting}
updateOn={updateOn}
fetchMeetings={fetchMeetings}
token={props.token}
/>
</Col>
{updateActive ? (
<MeetingEdit
meetingToUpdate={meetingToUpdate}
updateOff={updateOff}
token={props.token}
fetchMeetings={fetchMeetings}
/>
) : (
<></>
)}
</Row>
</Container>
</Div>
);
};
export default MeetingMain;
So I'm a bit at a loss on what's causing this. Any help would be appreciated.
Issue
You're defining a styled component inside your functional component, this means it's a new component each render cycle. In other words, it is a new component and mounted & rendered versus just being rerendered when state updates from the onChange handler.
Define Styled Components outside of the render method
It is important to define your styled components outside of the render
method, otherwise it will be recreated on every single render pass.
Defining a styled component within the render method will thwart
caching and drastically slow down rendering speed, and should be
avoided.
Recall: The entire body of a functional component IS the render "method".
Solution
Declare the Div component outside MeetingMain so it is a stable component reference.
const Div = styled.div`
background-color: #363136;
opacity: 0.8;
border-radius: 5px;
padding-top: 10px;
padding-left: 10px;
`;
const MeetingMain = (props) => {
const [meetings, setMeetings] = useState([]);
const [updateActive, setUpdateActive] = useState(false);
const [meetingToUpdate, setMeetingToUpdate] = useState({});
const [searchField, setSearchField] = useState("");
You should move Div outside of your MeetingMain component as below.
const Div = styled.div`
background-color: #363136;
opacity: 0.8;
border-radius: 5px;
padding-top: 10px;
padding-left: 10px;
`;
const MeetingMain = (props) => {
...
}
Check it out here

React Context values not referenced correctly in dynamic element functions

I created a Context and hook to be able to see if areas of the application has been changed, and validate actions based on the current state in context.
Its called DirtyContext and the Implementation is used as follows:
const {isDirty, setDirtyContextFor} = useDirtyContext();
setDirtyContextFor(key) - Ads a key to a list, to mark something as dirty.
isDirty - Reports the current state of the application based on a memoized value that updates everytime something is removed or added to the list of keys.
I have a list of objects, that helps me create a set of dynamic elements on the page.
const thisFunctionWillLooseContextReference = (e) => {
e.preventDefault();
console.log('Context Value - IsDirty: ', isDirty)
};
const [buttons, setButtons] = useState(() => {
return [{onClick: thisFunctionWillLooseContextReference}]
});
This is tied together in the UI using the following:
const renderButtons = () => {
return buttons.map((btn, index) => (
<button onClick={btn.onClick}>Button-{index}</button>
));
}
Even if the context value isDirty is set to true, the function passed to the button, always just reports the initial value of isDirty.
Would appreciate any help on why this is happening, and how i can get the expected results (which is the correct/current value of isDirty)
Codepen - Have a look at the console when clicking the buttons:
(Code and implementation details are reduced to the smallest reproducable state)
const { useState, useMemo } = React;
const DirtyContext = React.createContext();
const DirtyContextProvider = ({ children }) => {
const [dirtyList, setDirtyList] = useState(new Set());
const isDirty = useMemo(() => {
return dirtyList.size > 0;
}, [dirtyList]);
function setDirtyStateFor(componentName) {
const newDirtyList = new Set(dirtyList);
newDirtyList.add(componentName);
setDirtyList(newDirtyList);
}
return (<DirtyContext.Provider value={{
setDirtyStateFor,
isDirty,
}}>
{children}
</DirtyContext.Provider>);
};
const useDirtyContext = () => React.useContext(DirtyContext);
const MyDirtyLittleApp = () => {
const {isDirty, setDirtyStateFor} = useDirtyContext();
const [input, setValue] = useState("");
const thisFunctionWillLooseContextReference = (e) => {
e.preventDefault();
console.log('Context Value - IsDirty: ', isDirty)
};
const [buttons, setButtons] = useState(() => {
return [{onClick: thisFunctionWillLooseContextReference}]
});
function handleInput(event) {
setValue(event.target.value);
setDirtyStateFor('MyDirtyLittleApp');
}
function updateInput(event) {
event.preventDefault();
console.log('Am i dirty ?', isDirty)
}
const renderButtons = () => {
return buttons.map((btn, index) => (
<button class="button is-dark" data-reactstuff={isDirty} onClick={btn.onClick}>btn {index}</button>
));
}
return (
<React.Fragment>
<h1>{isDirty ? 'I`m Dirty': 'I`m Clean'}</h1>
<form className="form">
<div class="field">
<label for="name-1">Update DirtyContext</label>
<div class="control">
<input type="text" value={input} name="name-1" onChange={handleInput} class="input"/>
</div>
</div>
<div class="field">
<div class="control">
<button onClick={updateInput} class="button is-dark">Save</button>
{renderButtons()}
</div>
<control>
<h5>Check console for results when clicking on the buttons</h5>
</control>
</div>
</form>
</React.Fragment>
)
}
const App = () => {
return (
<DirtyContextProvider>
<div className="box">
<MyDirtyLittleApp />
</div>
</DirtyContextProvider>
)
}
ReactDOM.render(<App />,
document.getElementById("root"))
body {
height: 100vh;
margin: 0;
display: grid;
place-items: center;
}
.box {
width: 300px;
h1 {
font-size: 20px;
margin: 0 0 1rem 0;
}
h5 {
font-size: 12px;
}
}
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.4/css/bulma.min.css" type="text/css" />
<script src="https://unpkg.com/react#16.13.1/umd/react.production.min.js"></script>
<script src="https://unpkg.com/react-dom#16.13.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
https://codepen.io/Cnordbo/pen/zYqwVRL

Categories

Resources