I have an onClick event inside render() that won't work, I've tried (clearly not) everything.
What am I doing wrong?
render() {
const Card = ({backgroundColor, id, state}) =>{
const style = {
width: '100px',
height:'100px',
backgroundColor: backgroundColor,
key: id,
state: state,
}
return <div style={style} />
}
const finalCards = this.state.cards.map((n) =>(
<Card backgroundColor={n.hiddenColor} key={n.id} state={n.state} onClick={() => alert('clicked')} />
));
return (
<div className="App">
{finalCards}
</div>
);
}
}
Since Card is a separate component, you should keep it declare it outside of your render method.
But the problem is that you're passing an onClick function to a component but you're not hooking up that onClick function to the HTML element (in this case the <div />).
This should work
const Card = ({backgroundColor, id, state, onClick}) =>{
const style = {
width: '100px',
height:'100px',
backgroundColor: backgroundColor,
key: id,
state: state,
}
return <div style={style} onClick={onClick} />
}
Related
I'm trying develop a little app in which on you can select multiple music album, using Next.js.
I display my albums like the image below, and I would like to add a check mark when clicked and hide it when clicked again.
My code looks like that :
import Image from "next/image";
import {Card,CardActionArea} from "#mui/material";
import { container, card } from "../styles/forms.module.css";
import album from "../public/album.json"
export default function Album() {
const albumList = {} ;
function addAlbum(albumId, image){
if ( !(albumId in albumList) ){
albumList[albumId] = true;
//display check on image
}
else{
delete albumList[albumId]
//hide check on image
}
console.log(albumList)
}
return (
<div className={container}>
{Object.keys(album.albums.items).map((image) => (
<Card className={card}>
<CardActionArea onClick={() => addAlbum(album.albums.items[image].id)}>
<Image alt={album.albums.items[image].artists[0].name} width="100%" height="100%" src={album.albums.items[image].images[1].url} />
</CardActionArea>
</Card>
))}
</div>
);
}
I know I should use useState to do so, but how can I use it for each one of my albums?
Sorry if it's a dumb question, I'm new with Hook stuff.
I think there are a few ways to go about this, but here is a way to explain the useState in a way that fits the question. CodeSandbox
For simplicity I made a Card component that knowns if it has been clicked or not and determines wither or not it should show the checkmark. Then if that component is clicked again a clickhandler from the parent is fired. This clickhandle moves the Card into a different state array to be handled.
The main Component:
export default function App() {
const [unselectedCards, setUnselectedCards] = useState([
"Car",
"Truck",
"Van",
"Scooter"
]);
const [selectedCards, setSelectedCards] = useState([]);
const addCard = (title) => {
const temp = unselectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setUnselectedCards(temp);
setSelectedCards([...selectedCards, title]);
};
const removeCard = (title) => {
console.log("title", title);
const temp = selectedCards;
const index = temp.indexOf(title);
temp.splice(index, 1);
setSelectedCards(temp);
setUnselectedCards([...unselectedCards, title]);
};
return (
<div className="App">
<h1>Current Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{unselectedCards.map((title) => (
<Card title={title} onClickHandler={addCard} key={title} />
))}
</div>
<h1>Selected Cards</h1>
<div style={{ display: "flex", columnGap: "12px" }}>
{selectedCards.map((title) => (
<Card title={title} onClickHandler={removeCard} key={title} />
))}
</div>
</div>
);
}
The Card Component
export const Card = ({ onClickHandler, title }) => {
const [checked, setChecked] = useState(false);
const handleClickEvent = (onClickHandler, title, checked) => {
if (checked) {
onClickHandler(title);
} else {
setChecked(true);
}
};
return (
<div
style={{
width: "200px",
height: "250px",
background: "blue",
position: "relative"
}}
onClick={() => handleClickEvent(onClickHandler, title, checked)}
>
{checked ? (
<div
id="checkmark"
style={{ position: "absolute", left: "5px", top: "5px" }}
></div>
) : null}
<h3>{title}</h3>
</div>
);
};
I tried to make the useState actions as simple as possible with just a string array to help you see how it is used and then you can apply it to your own system.
You do not need to have a state for each album, you just need to set albumList as a state:
const [albumList, setAlbumList] = setState({});
function addAlbum(albumId, image) {
const newList = {...albumList};
if(!(albumId in albumList)) {
newList[albumId] = true;
} else {
delete albumList[albumId]
}
setAlbumList(newList);
}
And then in your loop you can make a condition to display the check mark or not by checking if the id is in albumList.
passing AgentDialogBox with prop reload to AgGridReactTable component
Yes, you can pass a component as props to another component in various ways.
First way,
function Wrapper({button: Button}) {
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Button />
</div>
);
}
export default function App() {
const Button = () => {
return <button onClick={() => console.log('button clicked')}>Click</button>;
};
// 👇️ pass button as props to the Wrapper component
return (
<div>
<Wrapper button={Button} />
</div>
);
}
In the above example, a component is passed as a prop to a child component.
The Wrapper component had to rename the prop from button to Button (capital first letter) because all component names must start with a capital letter.
Second Way,
function Wrapper({button}) {
return (
<div
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{button}
</div>
);
}
export default function App() {
const Button = ({text}) => {
return (
<button onClick={() => console.log('button clicked')}>{text}</button>
);
};
return (
<div>
<Wrapper button={<Button text="Some button text" />} />
</div>
);
}
In this example, we directly set the props of the Button component when passing it to the Wrapper component.
We are not passing the actual component function, instead, we are passing the return value of the Button component.
This means that we have to use the prop as {button}, instead of in our Wrapper component.
Now you can change your code using any of the above 2 mentioned ways, I'm using the second way here
const AgriReactTable = ({agentDetailsDialogBox, URL, columnDefs, reload}) => {
return (
<div>
{agentDetailsDialogBox}
</div>
)
}
const App = () => {
const [reload, setReload] = useState()
const AgentDetailsDialogBox = () => { return <div>{someContent}</div>}
return (
<div>
<AgriReactTable agentDetailsBox={<AgentDetailsBox reload={setReload} URL={apis.ADMIN_ALL_AGENT} columnDefs={columnDef} reload={reload}/>} />
</div>
)
}
Reference -> https://bobbyhadz.com/blog/react-pass-component-as-prop#:~:text=You%20can%20pass%20a%20component,assigned%20to%20the%20children%20prop.
I have a few buttons and "view all" button. The individual buttons load the coresponding data of that index or will show all the data by clicking the "view all" button. Problem I am running into is when I click my "view all" button in the parent it's not updating the state in the child component. On mounting it works as normal but on event handler in the "view all" it doesn't update. Any thoughts on where I am going wrong here?
JS:
...
const Context = createContext(false);
const useStyles = makeStyles((theme) => ({
root: {
display: "flex",
"& > *": {
margin: theme.spacing(1)
}
},
orange: {
color: theme.palette.getContrastText(deepOrange[500]),
backgroundColor: deepOrange[500],
border: "4px solid black"
},
info: {
margin: "10px"
},
wrapper: {
display: "flex"
},
contentWrapper: {
display: "flex",
flexDirection: "column"
},
elWrapper: {
opacity: 0,
"&.active": {
opacity: 1
}
}
}));
const ToggleItem = ({ id, styles, discription }) => {
const { activeViewAll, handleChange } = useContext(Context);
const [toggleThisButton, setToggleThisButton] = useState();
const handleClick = () => {
setToggleThisButton((prev) => !prev);
handleChange(discription, !toggleThisButton);
};
return (
<>
<Avatar
className={toggleThisButton && !activeViewAll ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(toggleThisButton)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item, idx) => (
<div key={idx}>Content {item}</div>
))}
</div>
);
};
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(false);
useEffect(() => {
setActiveViewAll(true);
setSelected([...data]);
}, []);
const handleChange = (val, action) => {
let newVal = [];
if (activeViewAll) {
selected.splice(0, 3);
setActiveViewAll(false);
}
if (action) {
newVal = [...selected, val];
} else {
// If toggle off, then remove content from selected state
newVal = selected.filter((v) => v !== val);
}
console.log("action", action);
setSelected(newVal);
};
const handleViewAll = () => {
console.log("all clicked");
setActiveViewAll(true);
setSelected([...data]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer styles={classes} selected={selected} />
</div>
</Context.Provider>
);
}
Codesanbox:
https://codesandbox.io/s/72166087-forked-jvn59i?file=/src/App.js:260-3117
Issue
The issue seems to be that you are mixing up the management of the boolean activeViewAll state with the selected state.
Solution
When activeViewAll is true, pass the data array as the selected prop value to the ToggleContainer component, otherwise pass what is actually selected, the selected state.
Simplify the handlers. The handleViewAll callback only toggles the view all state to true, and the handleChange callback toggles the view all state back to false and selects/deselects the data item.
function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]); // none selected b/c view all true
const [activeViewAll, setActiveViewAll] = useState(true); // initially view all
const handleChange = (val, action) => {
setActiveViewAll(false); // deselect view all
setSelected(selected => {
if (action) {
return [...selected, val];
} else {
return selected.filter(v => v !== val)
}
});
};
const handleViewAll = () => {
setActiveViewAll(true); // select view all
};
return (
<Context.Provider value={{ activeViewAll, handleChange }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={id}>
<ToggleItem id={id} styles={classes} discription={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected} // pass all data, or selected only
/>
</div>
</Context.Provider>
);
}
In the ToggleContainer don't use the array index as the React key since you are mutating the array. Use the element value since they are unique and changing the order/index doesn't affect the value.
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update
Since it is now understood that you want to not remember what was previously selected before toggling activeViewAll then when toggling true clear the selected state array. Instead of duplicating the selected state in the children components, pass the selected array in the context and computed a derived isSelected state. This maintains a single source of truth for what is selected and removes the need to "synchronize" state between components.
const ToggleItem = ({ id, styles, description }) => {
const { handleChange, selected } = useContext(Context);
const isSelected = selected.includes(description);
const handleClick = () => {
handleChange(description);
};
return (
<>
<Avatar
className={isSelected ? styles.orange : ""}
onClick={handleClick}
>
{id}
</Avatar>
<p>{JSON.stringify(isSelected)}</p>
</>
);
};
const ToggleContainer = ({ className, selected }) => {
return (
<div className={className}>
{selected.map((item) => (
<div key={item}>Content {item}</div>
))}
</div>
);
};
Update the handleChange component to take only the selected value and determine if it needs to add/remove the value.
export default function App() {
const data = ["first", "second", "third"];
const classes = useStyles();
const [selected, setSelected] = useState([]);
const [activeViewAll, setActiveViewAll] = useState(true);
const handleChange = (val) => {
setActiveViewAll(false);
setSelected((selected) => {
if (selected.includes(val)) {
return selected.filter((v) => v !== val);
} else {
return [...selected, val];
}
});
};
const handleViewAll = () => {
setActiveViewAll(true);
setSelected([]);
};
return (
<Context.Provider value={{ activeViewAll, handleChange, selected }}>
<div className={classes.wrapper}>
<Avatar
className={activeViewAll ? classes.orange : null}
onClick={handleViewAll}
>
<span style={{ fontSize: "0.75rem", textAlign: "center" }}>
View All
</span>
</Avatar>
{data.map((d, id) => {
return (
<div key={d}>
<ToggleItem id={id} styles={classes} description={d} />
</div>
);
})}
</div>
<div className={classes.contentWrapper}>
<ToggleContainer
styles={classes}
selected={activeViewAll ? data : selected}
/>
</div>
</Context.Provider>
);
}
I want to link from one view to another and then scroll to a specific element. I'm not intrested in any animations, only want to have the element in view. The link from one view to another is done through react router.
I guess I could somehow create references on the elements I want to scroll to and pass them to the other view, but don't know if that's the correct approach?
A simple example. (Not working, but hopefully you understand what I want to achieve)
const ViewOne = () => {
const navigate = useNavigate(); // From react-router v6
return (
<p onClick={() =>
{
navigate("ViewTwo");
// What more do I have to add?
}}>
Link to section two, in view two
</p>
);
}
export default ViewOne;
const ViewTwo = () => {
return (
<>
<section style={{height: "100vh"}}></section>
<section style={{height: "100vh"}}>
Scroll here?
</section>
<section style={{height: "100vh"}}></section>
</>);
}
export default ViewTwo;
I'm using react-router-dom-v6
Give the sections you want to target and scroll to id attributes. Pass a target id in route state. Use a useEffect hook to target the element and scroll it into view.
Example:
const ViewOne = () => {
const navigate = useNavigate(); // From react-router v6
return (
<p
onClick={() => {
navigate("/viewtwo", { state: { targetId: "section2" } });
}}
>
Link to section two, in view two
</p>
);
};
...
const ViewTwo = () => {
const { state } = useLocation();
const { targetId } = state || {};
useEffect(() => {
const el = document.getElementById(targetId);
if (el) {
el.scrollIntoView();
}
}, [targetId]);
return (
<>
<section id="section1" style={{ height: "100vh" }}></section>
<section id="section2" style={{ height: "100vh" }}>
Scroll here?
</section>
<section id="section3" style={{ height: "100vh" }}></section>
</>
);
};
...
<Router>
<Routes>
...
<Route path="/viewone" element={<ViewOne />} />
<Route path="/viewtwo" element={<ViewTwo />} />
...
</Routes>
</Router>
you can use "useRef" to scroll to that position with click event or try useEffect for scroll to that position after component rendered.
const ViewTwo = () => {
const scroller = useRef(null)
const executeScroll = () => scroller.current.scrollIntoView()
return (
<>
<section style={{height: "100vh"}}></section>
<section ref={scroller} style={{height: "100vh"}}>
Scroll here?
</section>
<button onClick={executeScroll}> Click to scroll </button>
<section style={{height: "100vh"}}></section>
</>);
}
export default ViewTwo;
The callback function (lies in Images component) is responsible for making a state update. I'm passing that function as props to the Modal component, and within it it's being passed into the ModalPanel component.
That function is used to set the state property, display, to false which will close the modal. Currently, that function is not working as intended.
Image Component:
class Images extends Component {
state = {
display: false,
activeIndex: 0
};
handleModalDisplay = activeIndex => {
this.setState(() => {
return {
activeIndex,
display: true
};
});
};
closeModal = () => {
this.setState(() => {
return { display: false };
});
}
render() {
const { imageData, width } = this.props;
return (
<div>
{imageData.resources.map((image, index) => (
<a
key={index}
onClick={() => this.handleModalDisplay(index)}
>
<Modal
closeModal={this.closeModal}
display={this.state.display}
activeIndex={this.state.activeIndex}
selectedIndex={index}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</Modal>
</a>
))}
</div>
);
}
}
export default Images;
Modal Component:
const overlayStyle = {
position: 'fixed',
zIndex: '1',
paddingTop: '100px',
left: '0',
top: '0',
width: '100%',
height: '100%',
overflow: 'auto',
backgroundColor: 'rgba(0,0,0,0.9)'
};
const button = {
borderRadius: '5px',
backgroundColor: '#FFF',
zIndex: '10'
};
class ModalPanel extends Component {
render() {
const { display } = this.props;
console.log(display)
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
</div>
);
return <div>{display ? overlay : null}</div>;
}
}
class Modal extends Component {
render() {
const {
activeIndex,
children,
selectedIndex,
display,
closeModal
} = this.props;
let modalPanel = null;
if (activeIndex === selectedIndex) {
modalPanel = (
<ModalPanel display={this.props.display} closeModal={this.props.closeModal} />
);
}
return (
<div>
{modalPanel}
{children}
</div>
);
}
}
export default Modal;
links to code
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Images.js
https://github.com/philmein23/chez_portfolio/blob/chez_portfolio/components/Modal.js
You're dealing with this modal through a very non-react and hacky way.
Essentially, in your approach, all the modals are always there, and when you click on image, ALL modals display state becomes true, and you match the index number to decide which content to show.
I suspect it's not working due to the multiple children of same key in Modal or Modal Panel.
I strongly suggest you to ditch current approach. Here's my suggestions:
Only a single <Modal/> in Images component.
Add selectedImage state to your Images component. Every time you click on an image, you set selectedImage to that clicked image object.
Pass selectedImage down to Modal to display the content you want.
This way, there is only ONE modal rendered at all time. The content changes dynamically depending on what image you click.
This is the working code I tweaked from your repo:
(I'm not sure what to display as Modal content so I display public_id of image)
Images Component
class Images extends Component {
state = {
display: false,
selectedImage: null
};
handleModalDisplay = selectedImage => {
this.setState({
selectedImage,
display: true
})
};
closeModal = () => {
//shorter way of writing setState
this.setState({display: false})
}
render() {
const { imageData, width } = this.props;
return (
<div>
<Modal
closeModal={this.closeModal}
display={this.state.display}
selectedImage={this.state.selectedImage}
/>
{imageData.resources.map((image, index) => (
<a
//Only use index as key as last resort
key={ image.public_id }
onClick={() => this.handleModalDisplay(image)}
>
<Image
cloudName={CLOUDINARY.CLOUDNAME}
publicId={image.public_id}
width={width}
crop={CLOUDINARY.CROP_TYPE}
/>
</a>
))}
</div>
);
}
}
Modal Component
class Modal extends Component {
render() {
const { display, closeModal, selectedImage } = this.props;
const overlayContent = () => {
if (!selectedImage) return null; //for when no image is selected
return (
//Here you dynamically display the content of modal using selectedImage
<h1 style={{color: 'white'}}>{selectedImage.public_id}</h1>
)
}
const overlay = (
<div style={overlayStyle}>
<button style={button} onClick={this.props.closeModal}>
X
</button>
{
//Show Modal Content
overlayContent()
}
</div>
);
return <div>{display ? overlay : null}</div>;
}
}