How to change style property in react hooks on function call - reactjs

On function call i was changing the image style using useState hooks
I was sending these property as an props
basically i want to a function which should contain style property for img and pass it to another component as propsstyle = {{opacity: ".3"}}
import React, { useState } from 'react';
import BackgroundImage from '../Image/Homepage/background.png'
const HomePage = () => {
const [modalShow, setModalShow] = useState(false);
const [image, setImage] = useState(BackgroundImage)
return (
<div>
<img src={image} className="first-image" alt="backGroundImage" />
</div>
<Modals
show={modalShow}
onhide={() => setModalShow(false)}
sendImages = {() => setImage( style = {{opacity: ".3"}} )}
/>
)}
this is throwing an error
sendImages = {() => setImage( style = {{opacity: ".3"}} )}
I think this not right approach

It looks like you want to make opacity dynamic, instead you manipulate image src...
import React, { useState } from 'react';
import BackgroundImage from '../Image/Homepage/background.png'
const HomePage = () => {
const [modalShow, setModalShow] = useState(false);
const [image, setImage] = useState(BackgroundImage);
const [opacity, setOpacity] = useState(1);
return (
<>
<div>
<img src={image} className="first-image" style={{opacity}} alt="backGroundImage" />
</div>
<Modals
show={modalShow}
onhide={() => setModalShow(false)}
sendImages = {() => setOpacity(0.3)}
/>
</>
)}

If you only wish to update the style property on function call, you must store the style property in state and not the image. Also the syntax for setImage is incorrect in your code
import React, { useState } from 'react';
import BackgroundImage from '../Image/Homepage/background.png'
const HomePage = () => {
const [modalShow, setModalShow] = useState(false);
const [imageStyle, setImageStyle] = useState({})
return (
<>
<div>
<img src={BackgroundImage} style={imageStyle} className="first-image" alt="backGroundImage" />
</div>
<Modals
show={modalShow}
onhide={() => setModalShow(false)}
sendImages = {() => setImageStyle({opacity: ".3"})}
/>
</>
)}
NOTE: Also please note that state updaters with hooks do not merge the values but override it. So if you wish to update only certain properties make use of state updater callback method to return the merged values yourself

Related

How do I pass a UseState to another jsx component?

I am working on a react Movie app and I want to pass a showModal useState to another component as I have a conditional on the component with my showModal like this:
const [showModal, setShowModal] = useState(false);
return (
<>
{showModal ? (<div>Modal..</div>) : null}
</>
Then on the other component I want to pass the hook to is something like this:
... <button
type="button"
onClick={() => setShowModal(true)}
className="my-4 border text-white py-2 px-4 rounded-3xl border-blue-600 bg-blue-600 shadow-lg"
>
Watch trailer
</button>...
Thanks in advance!
You can totally pass the setShowModal as a props.
const [showModal, setShowModal] = useState(false);
return (
<>
{showModal ? (<Modal setShowModal={setShowModal} />) : null}
</>
Write this hook : const [showModal, setShowModal] = useState(false); in the parent component.
And then pass the function setShowModal in props of your modal componant.
export default function YourModal({setShowModal}){
}
Your modal component should look like this and you can use the function setShowModal inside this component
Well the best approach here is to use react context api , since you don't write your code base and components tree it's very difficult to guess which one belongs to other!
Anyway you could have this kind of context called modalContext :
import React from 'react';
import { useState, createContext, useContext } from 'react';
const ModalContext = createContext(null);
function ModalProvider({ children }) {
const [showModal, setShowModal] = useState(false);
return (
<ModalContext.Provider value={{ showModal, setShowModal }}>
{children}
</ModalContext.Provider>
);
}
function useModal() {
const context = useContext(ModalContext);
if (context === undefined) {
throw new Error('useModal must be used within a ModalProvider');
}
return context;
}
export { ModalProvider, useModal };
and then wrap your application with the provider :
import {ModalProvider} from '...(path to context)/modalContext';
<ModalProvider>
<App />
</ModalProvider>
then where ever you need modalShow or the setter setModalShow you can call it like this easily :
import {useModal} from '..path to /modalContext';
function SomeComponent() {
const {showModal, setShowModal} = useModal();
return (
<>
{showModal ? (
<ChildComponent toggleModal={setShowModal}>Modal..
</ChildComponent>
) : null}
</>
);
}

Display data in modal

I would like to pass several data in a modal. My modal works fine but does not display the data I want. I would like to display the data BY ID, but I get all the data. This is my code :
App.js :
import React, { useState, useEffect } from "react";
import Modal from "./Modal";
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const App = (props) => {
const [image, setImage] = useState([])
return (
<div>
{image.map((image) => {
return (
<div key={image.id}>
<img src={...}
alt={image.title}
/>
<ExpandMoreIcon onClick={handleClick} />
</div>
)
})}
</div>
);
}
export default App;
Modal.js :
import React from 'react';
const Modal = ({ showModal, image }) => {
console.log("result", image);
return (
<div className="modal" >
<div className='modal__content'>
<h1 className='modal__title'>{image.title}</h1>
<p className='modal__description'>{image.description}</h1>
</div>
</div>
);
}
export default Modal;
I think the problem comes from here image={image} in App.js because I get 8 tables in console.log("result", movie);
{showModal && <Modal showModal={handleClick} image={image} />}
Your problem is your modal state is true/false value, so that's why once you trigger showModal, it will show all modals according to your number of images
The fix can be
Note that I modified your state name to align with new state values
const [modalImage, setModalImage] = useState(); //undefined as no image selected
const handleClick = (imageId) => {
setModalImage(imageId);
};
Your elements will be like below
<ExpandMoreIcon onClick={() => handleClick(image.id)} />
{modalImage === image.id && <Modal showModal={() => handleClick(image.id)} image={image} />}
If you want to close the modal, you can reset modalImage state
onClick={() => handleClick(undefined)}
I just noticed that you also can move your modal out of the image loop, and potentially you can pass the entire image object to the modal too
The full implementation
import React, { useState, useEffect } from "react";
import Modal from "./Modal";
import PlayCircleIcon from '#mui/icons-material/PlayCircle';
const App = (props) => {
const [image, setImage] = useState([])
const [modalImage, setModalImage] = useState(); //no image selected
const handleClick = (image) => {
setModalImage(image);
};
useEffect(() => {
...
}, []);
return (
<div>
{image.map((image) => {
return (
<div key={image.id}>
<img src={...}
alt={image.title}
/>
<ExpandMoreIcon onClick={() => handleClick(image)} />
</div>
)
})}
{modalImage && <Modal showModal={() => handleClick(modalImage)} image={modalImage} />}
</div>
);
}
export default App;
Both the map item and the image state array are named image in your code here;
image.map((image) => {
/// your code
})
which could be the ambiguity that led to showing the whole array of data instead of one image in the array.

Bad setState call inside offcanvas component

I'm getting a bad setState() error inside the sidebar bootstrap offcanvas component. It says it can't update the component App() while rendering a different component sidebar() Sorry but I had to delete a large section of my code which was a fetch call. Thanks.
Error: index.js:1 Warning: Cannot update a component (App) while rendering a different component (Sidebar). To locate the bad setState() call inside Sidebar, follow the stack trace as described in
App.js
import React, {useEffect, useState} from 'react';
import './App.css';
import MovieTag from "./components/MovieTag/MovieTag";
import Sidebar from "./components/MovieTag/Sidebar";
interface Movie {
id: number,
poster_path: string,
title: number
}
function App() {
const [movies, setMovies] = useState([]);
const [genre, setGenre] = useState(0);
const [sort, setSort] = useState("popularity.desc");
const [page, setPage] = useState(1);
function GetMovies(genreId: number, sortBy: string, page: number){
setGenre(genreId);
setSort(sortBy);
setPage(page);
return (
<div className={'container'}>
<Sidebar filterByGenre={(genre: number) => {
setGenre(genre);
}}
sortBy={(sort: string) => {
setSort(sort);
}}
pageFilter={(page: number) => {
setPage(page);
}}
/>
<div className={'row'}>
{movies.map((movie: Movie) => {
return (
<MovieTag key={movie.id}
poster={movie.poster_path}
title={movie.title}
/>
)
})}
</div>
</div>
);
}
export default App;
Sidebar.js
function Sidebar(props: any) {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<React.Fragment>
<Button variant={'secondary'} onClick={handleShow}>Filter</Button>
<Offcanvas show={show} onHide={handleClose}>
<Offcanvas.Header>
<Offcanvas.Title>Filter</Offcanvas.Title>
</Offcanvas.Header>
<Offcanvas.Body>
<DropdownButton title={'Genre'} drop={'end'}>
<Dropdown.Item eventKey={1}><button onClick={props.filterByGenre(28)}>Action</button></Dropdown.Item>
</DropdownButton>
</Offcanvas.Body>
</Offcanvas>
</React.Fragment>
)
}
The problem is on Sidebar component which is calling a setState of its parent component while rendering on <Dropdown.Item eventKey={1}><button onClick={props.filterByGenre(28)}>Action</button></Dropdown.Item>
If you need to call the function onClick, then create an arrow function to it.
onClick={() => props.filterByGenre(28)}

How to get acces to a child useState in React?

I'm trying to done a form in react that have subcomponents for uploaded images (to do a preview and do it more beautyfull) but the thing is that I can't access to the useState of the child where is the image that I need to send to de backend.
Here is the code of the subcomponent and in the useState I need to acces throught the parent to the image:
import React, { useState, Fragment } from "react";
import {
Layout,
Container,
BoxUpload,
ContainerUploadImage,
TextUploadImage,
LabelUploadImage,
ImagePreview,
} from "./ImageUploadElements";
import UploadPhoto from "../../../images/upload.svg";
import CloseIcon from "../../../images/close.svg";
const ImageUpload = ({text}) => {
const [image, setImage] = useState("");
const [isUploaded, setIsUploaded] = useState(false);
const handleImageChange = (e) => {
if (e.target.files && e.target.files[0]) {
let reader = new FileReader();
reader.onload = (e) => {
setImage(e.target.result);
setIsUploaded(true);
};
reader.readAsDataURL(e.target.files[0]);
}
};
return (
<Layout>
<Container>
<h2>{text}</h2>
<BoxUpload>
<div className="image-upload">
{isUploaded ? (
<ImagePreview>
<img
className="close-icon"
src={CloseIcon}
alt="CloseIcon"
onClick={() => {
setIsUploaded(false);
setImage(null);
}}
/>
<img
src={image}
className="uploaded-image"
draggable={false}
alt="progress-uploaded"
/>
</ImagePreview>
) : (
<Fragment>
<LabelUploadImage htmlFor="upload-input">
<ContainerUploadImage
src={UploadPhoto}
alt="Upload Icon"
draggable={false}
/>
<TextUploadImage>Click to upload image</TextUploadImage>
</LabelUploadImage>
<input
type="file"
name="upload-input"
accept=".jpg,.jpeg,.gif,.png,.mov,.mp4"
onChange={handleImageChange}
/>
</Fragment>
)}
</div>
</BoxUpload>
</Container>
</Layout>
);
};
export default ImageUpload;
And here in that upload form component is where I need to get acces to this image to send it with axios to backend:
import React, { Fragment, useState } from "react";
import {
Container,
FormWrap,
FormContent,
Form,
FormH1,
FormLabel,
FormInput,
FormButton,
FormErrorWrap,
FormError,
FormErrorText,
PhotoWrap
} from "./UploadElements";
import ImageUpload from "../ImageUpload";
import { frontPhotoText, sidePhotoText, backPhotoText } from "./Data";
const Upload = () => {
const [weight, setWeight] = useState("");
const [uploadErrors, setUploadErrors] = useState([{}]);
const upload = (e) => {
e.preventDefault();
// Here will go the axios peticiĆ³n with the wight and the three images uploaded.
}
return (
<Fragment>
<Container>
<FormWrap>
<FormContent>
<Form onSubmit={upload}>
<FormH1>Upload New Progress</FormH1>
<FormLabel htmlFor="weight">Weight</FormLabel>
<FormInput
onChange={(e) => setWeight(e.target.value)}
type="number"
value={weight}
id="weight"
required
/>
<PhotoWrap>
<ImageUpload {...frontPhotoText}/>
<ImageUpload {...sidePhotoText}/>
<ImageUpload {...backPhotoText}/>
</PhotoWrap>
<FormErrorWrap>
{uploadErrors ? (
uploadErrors.map((err, index) => (
<FormError key={index}>
<FormErrorText>{err.msg}</FormErrorText>
</FormError>
))
) : (
<Fragment></Fragment>
)}
</FormErrorWrap>
<FormButton>Upload</FormButton>
</Form>
</FormContent>
</FormWrap>
</Container>
</Fragment>
);
};
export default Upload;
But I don't know how can I get this images throught the parent, if anyone can help I'll be very gratefull, thanks!!!
You can use a combination of forwardRef and useImperativeHandle to expose out a function from the child component that a parent component can invoke.
Child - Import and decorate the child component with forwardRef and use the useImperativeHandle to expose a getImage function that returns the current image state.
import React, { useState, Fragment, forwardRef } from "react";
...
const ImageUpload = forwardRef(({text}, ref) => {
const [image, setImage] = useState("");
const [isUploaded, setIsUploaded] = useState(false);
useImperativeHandle(ref, () => ({
getImage: () => image,
}));
const handleImageChange = (e) => {
...
};
return (
...
);
});
Parent - Create a React ref to pass to ImageUpload and in the callback access the current ref value and invoke the function.
import React, { Fragment, useState, useRef } from "react";
...
const Upload = () => {
const [weight, setWeight] = useState("");
const imageUploadFrontRef = useRef();
const imageUploadSideRef = useRef();
const imageUploadBackRef = useRef();
const [uploadErrors, setUploadErrors] = useState([{}]);
const upload = (e) => {
e.preventDefault();
const imageFront = imageUploadFrontRef.current.getImage();
const imageSide = imageUploadSideRef.current.getImage();
const imageBack = imageUploadBackRef.current.getImage();
// do with the images what you need.
}
return (
<Fragment>
<Container>
<FormWrap>
<FormContent>
<Form onSubmit={upload}>
...
<PhotoWrap>
<ImageUpload ref={imageUploadFrontRef} {...frontPhotoText} />
<ImageUpload ref={imageUploadSideRef} {...sidePhotoText} />
<ImageUpload ref={imageUploadBackRef} {...backPhotoText} />
</PhotoWrap>
...
</Form>
</FormContent>
</FormWrap>
</Container>
</Fragment>
);
};

Change the state of one item in a map in react

I have a page that displays images fetched with an api call using the map method. Now what I want to do is to toggle the like or unlike state of FavoriteIcon by clicking it.
When I try to implement that using the useState hook, if I click on the FavoriteIcon in a single image, all the images are set to the same state. I want only the clicked image to change its state.
import React, { useState, useEffect } from "react";
import FavoriteIcon from "#material-ui/icons/Favorite";
import FavoriteBorderIcon from "#material-ui/icons/FavoriteBorder";
// services
import medias from "../../services/mediaServices";
function Favorites({ fetchUrl, catagory }) {
const classes = useStyles();
const [images, setImages] = useState([{}]);
const [favIcon, setFavIcon] = useState();
useEffect(() => {
async function getData() {
const request = await medias.getMedias(fetchUrl);
setImages(request.data.message);
}
getData();
}, [fetchUrl, enqueueSnackbar, closeSnackbar]);
return (
<div className={classes.favorites}>
<div className={classes.favorites__items}>
{images &&
images.map(image => {
return (
<div
elevation={3}
className={classes.favorites__posters}
style={
{
// border: "2px solid black"
}
}
>
<img
key={image.id}
src={image.thumbnailUrl}
alt={image.title}
/>
{favIcon ? (
<FavoriteIcon onClick={() => setFavIcon(false)} />
) : (
<FavoriteBorderIcon onClick={() => setFavIcon(true)} />
)}
</div>
);
})}
</div>
</div>
);
}
export default Favorites;
Without breaking it into more components, your favIcon state needs to contain the state of all your icons. It should be initialized with something like
images.map(image => {id: image.id, state: false})
and then you could change your onClick to just update the relevant part of state
setFavIcon([...favIcon].map(x => {if(x.id === image.id) {x.state = !x.state}; return x}))

Resources