Disable effect via props react-three-fiber - reactjs

I have scene created with react-three-fiber which has an EffectsComposer with a Glitch effect using react-postprocessing npm package.
I would like to toggle the active state of the Glitch effect using an html input checkbox, however, toggling the state of the active prop does not disable the effect. What's the correct way to disable effects on checkbox input?
I've set up a sandbox with the issue here
https://codesandbox.io/s/tender-star-lw07h?file=/src/App.tsx:0-1488
Here's the code.
import React, {
Suspense,
useRef,
useEffect,
useState,
createRef,
Fragment
} from "react";
import Outline from "./components/Outline";
import { EffectComposer, Glitch, Noise } from "react-postprocessing";
import { Canvas } from "react-three-fiber";
import { OrbitControls } from "drei";
import { Mesh } from "three";
import { GlitchMode } from "postprocessing";
const Box = (props: any) => (
<mesh {...props}>
<meshBasicMaterial attach="material" color="red" />
<boxGeometry args={[1, 1, 1]} attach="geometry" />
</mesh>
);
const Composer = ({ active }: any) => {
const ref = React.useRef();
return (
<EffectComposer>
<Glitch ref={ref} active={active} mode={GlitchMode.CONSTANT_WILD} />
</EffectComposer>
);
};
const App = () => {
const ref = useRef();
const [selection, select] = useState<Mesh>();
const [active, activate] = useState<boolean>(true);
useEffect(() => {
if (selection) {
console.log(ref.current);
}
}, [selection]);
return (
<Fragment>
<div style={{ background: "#fff" }}>
<label htmlFor="">Active {JSON.stringify(active)}</label>
<input
type="checkbox"
defaultChecked={active}
onChange={($event) => {
activate($event.target.checked);
}}
/>
</div>
<Canvas>
<Box onClick={(mesh) => select(mesh)} />
<Composer active={active} />
</Canvas>
</Fragment>
);
};

Just do
const App = () => {
...
<Canvas>
<Box .../>
{active && <Composer/>}
</Canvas>
and
const Composer = () => {
return (
<EffectComposer>
<Glitch active={true} mode={GlitchMode.CONSTANT_WILD} />
</EffectComposer>
);
};

Related

Trying to get Material ui dialog into contextualised modal

I have a modal component which I have put into context.
If I return html , some divs etc, then no problem, but
ideally I want to do this with the material-ui dialog.
But if I put in materail-ui dialog, as below,
then nothing is displayed.
The component:
import React, { useCallback, useEffect, useState } from 'react'
import {Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle} from "#material-ui/core";
import {useTranslation} from "react-i18next";
import {errorStyles} from "../errorStyle";
// Create a React context
const ModalContext = React.createContext(null);
//Declare the modal component
const Modal = ({ modal, unSetModal, errorClasses = errorStyles(),
title, subtitle, children, }) => {
useEffect(() => {
const bind = e => {
if (e.keyCode !== 27) {
return
}
if (document.activeElement && ['INPUT', 'SELECT'].includes(document.activeElement.tagName)) {
return
}
unSetModal()
}
document.addEventListener('keyup', bind)
return () => document.removeEventListener('keyup', bind)
}, [modal, unSetModal])
const { t } = useTranslation()
return (
<>
<Dialog
className={errorClasses.modal}
fullWidth
maxWidth='md'
aria-labelledby='max-width-dialog-title'
>
<DialogTitle id='max-width-dialog-title'
className={errorClasses.header}>{title}</DialogTitle>
<DialogContent>
<DialogContentText>{subtitle}</DialogContentText>
</DialogContent>
<DialogActions>
<button className={errorClasses.cancelButton}
onClick={unSetModal}>{t("Close")}</button>
</DialogActions>
</Dialog>
</>
)
}
const ModalProvider = props => {
const [modal, setModal] = useState()
const unSetModal = useCallback(() => {
setModal('')
}, [setModal])
return (
<ModalContext.Provider value={{ unSetModal, setModal }} {...props} >
{props.children}
{modal && <Modal modal={modal} unSetModal={unSetModal} />}
</ModalContext.Provider>
)
}
// useModal: shows and hides the Modal
const useModal = () => {
const context = React.useContext(ModalContext)
if (context === undefined) {
throw new Error('useModal must be used within a UserProvider')
}
return context
}
export { ModalProvider, useModal }
And in another component (an edit form)
import { useModal, ModalProvider } from '../context/modal-context'
.....
export function DeviceModal (props) {
const { setModal } = useModal()
...
some other markup
<div style={{ position: 'sticky', bottom:'0', width:'100%', height:'40px', paddingTop:'5px'}}>
<Grid container direction="row-reverse" item xs={12} alignItems="right">
<button className={classes.cancelButton}
onClick={() => { setModal(<></>)}} >
{'Cancel'}
</button>
</Grid>
</div>
Hope someone can help
Thanks
Answer:
By adding the following to the dialogprops, it is resolved.
open={true}
backdropComponent={Backdrop}
and also, getting it to display values from the parent.
const ModalProvider = props => {
const [modal, setModal] = useState();
const [title, setTitle] = useState();
const [errMsg, setErrMsg] = useState();
const unSetModal = useCallback(() => {
setModal('')
}, [setModal])
// Provide the Modals functions and state variables to the consuming parent component
return (
<ModalContext.Provider value={{ unSetModal, setModal, setTitle, setErrMsg }} {...props} >
{props.children}
{modal && <Modal modal={modal} unSetModal={unSetModal} title={title} errMsg={errMsg} />}
</ModalContext.Provider>
)}
In the parent use setTitle("yada yada") setErrMsg('more yerra yada");

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>
);
};

React Components are not refreshing when the prop data is changed

import './App.css';
import { MenuItem, FormControl, Select, Card, CardContent } from "#material-ui/core";
import Infobox from './Infobox';
import Map from './Map';
import Table from './Table';
import { sortData } from './util';
import LineGraph from './LineGraph';
import "leaflet/dist/leaflet.css";
function App() {
const [countries, setCountries] = useState([]);
const [country, setCountry] = useState("WorldWide");
const [countryInfo, setCountryInfo] = useState({});
const [tableData, setTableData] = useState([]);
const [type, setType] = useState("cases");
const [mapCenter, setMapCenter] = useState([-34, -64]);
const [mapZoom, setMapZoom] = useState(3);
useEffect(() => {
fetch("https://disease.sh/v3/covid-19/all").then(response => response.json()).then(data => {
setCountryInfo(data)
})
}, []);
useEffect(() => {
const getCountriesData = async () => {
await fetch("https://disease.sh/v3/covid-19/countries").then(response => response.json()).then(data => {
const countries = data.map(country => ({
name: country.country,
value: country.countryInfo.iso2
}));
const sortedData = sortData(data);
setTableData(sortedData);
setCountries(countries);
});
};
getCountriesData();
}, []);
const onCountryChange = async (event) => {
const countryCode = event.target.value;
const url = countryCode === "WorldWide" ? "https://disease.sh/v3/covid-19/all" : `https://disease.sh/v3/covid-19/countries/${countryCode}`
await fetch(url).then(response => response.json()).then(data => {
setCountry(countryCode);
setCountryInfo(data);
console.log(data);
setMapCenter([data.countryInfo.lat, data.countryInfo.long]);
// console.log("this is it: ", mapCenter);
setMapZoom(4);
});
};
return (
<div className="app">
<div className="app__left">
<div className="app__header">
<h1>Covid-19 Tracker</h1>
<FormControl className="app__dropdown">
<Select
variant="outlined"
onChange={onCountryChange}
value={country}
>
<MenuItem value="WorldWide">WorldWide</MenuItem>
{countries.map(country => (<MenuItem value={country.value}>{country.name}</MenuItem>))}
</Select>
</FormControl>
</div>
<div className="app__stats">
<Infobox
title="Cases"
cases={countryInfo.todayCases}
total={countryInfo.cases} />
<Infobox
title="Recovery"
cases={countryInfo.todayRecovered}
total={countryInfo.recovered} />
<Infobox
title="Death"
cases={countryInfo.todayDeaths}
total={countryInfo.deaths} />
</div>
<Map
center={mapCenter}
zoom={mapZoom}
/>
</div>
<Card className="app__right">
<CardContent>
<h3>Live case by country</h3>
<Table countries={tableData} />
<h3>WorldWide new cases</h3>
<LineGraph type={type} />
</CardContent>
</Card>
</div>
);
}
export default App;
I am having a problem in the Map component in the app__left div. I am passing two parameters
center and zoom. Now in the function onCountryChange whenever a country is selected from a drop down list it should update the variable mapCenter and mapZoom so that the props in the Map component changes and the map is re-rendered along with the new center and zoom. However even after changing the props the Map component is not re-rendering. I have checked the data after onCountryChange everything seems OK. Somehow the component is not updating itself with the new data.
import React from 'react';
import './Map.css';
import { MapContainer as LeafletMap, TileLayer } from 'react-leaflet';
function Map({center, zoom}) {
return (
<div className="map">
<LeafletMap center={center} zoom={zoom}>
<TileLayer
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
attribution='© OpenStreetMap contributors'
/>
</LeafletMap>
</div>
)
}
export default Map
This is the map file.
According to the documentation of react-leaflet, after you set these values for the first time, they won't listen to new updates.
Except for its children, MapContainer props are immutable: changing them after they have been set a first time will have no effect on the Map instance or its container.
Source: https://react-leaflet.js.org/docs/api-map
I think you'd have to imperatively update the center and zoom making use of the useMap hook.
They've got this example on the documentation as well:
https://react-leaflet.js.org/docs/api-map#hooks
import * as React from 'react';
import { MapContainer } from 'react-leaflet';
function MyComponent({ center, zoom }) {
const map = useMap()
React.useEffect(() => {
map.setView(center);
}, [center]);
React.useEffect(() => {
map.setZoom(zoom);
}, [zoom]);
}
function Map({ center, zoom }) {
return (
<MapContainer center={center} zoom={zoom}>
<MyComponent center={center} zoom={zoom} />
</MapContainer>
)
}

How to update Draft Value when click of Save Button?

I have a two Input form and a Paragraph, when I try to change the value of input the paragraph get updated, once the paragraph is updated I am trying to edit the paragraph with the help of drafts library, but once I update the paragraph and save it, it doesn't update the paragraph.
Please anyone Help me out to solve the problem
Codesandbox Link : Code
Context API
import React, { useState, createContext } from "react";
export const Contx = createContext();
export const ConProvider = ({ children }) => {
const [customerName, setCustomerName] = useState("");
const [amount, setAmount] = useState("");
const defaultValue = `<p>Hello ${
customerName === "" ? "User" : customerName
},</p>
<p>Please Click on the link below to pay the order - <strong>${amount}</strong> . </p>
<p>Click hear to pay</p>
<br/>
<p>Thanks and Regards</p>
<p>testUser</p>`;
const [draftValue, setDraftValue] = useState(defaultValue);
return (
<Contx.Provider
value={{
defaultValue,
setCustomerName,
setAmount,
customerName,
amount,
setDraftValue,
draftValue
}}
>
{children}
</Contx.Provider>
);
};
homePage
import React, { useContext, useState } from "react";
import ReactDOM from "react-dom";
import { ConProvider, Contx } from "../ContextApi";
import Data from "./Component/Data/Data";
import NewDraft from "./Component/Data/NewDraft";
import Modal from "./Component/Data/Modal";
import "./styles.css";
function App() {
const { defaultValue, setDraftValue, draftValue } = useContext(Contx);
// console.log("defaultValue", defaultValue);
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
return (
<div className="App">
<Data />
<Modal handleClose={handleClose} show={show}>
<NewDraft
prsFunc={setDraftValue}
handleClose={handleClose}
defaultValueEmpty={false}
defaultValue={defaultValue}
/>
</Modal>
<div
className="templateStyle p-2"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{
__html: draftValue && draftValue
}}
/>
<button onClick={handleShow}>Edit</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<ConProvider>
<App />
</ConProvider>,
rootElement
);
Input Form
import React, { useContext } from "react";
import { Contx } from "../../../ContextApi";
export default function Data() {
const {
setCustomerName,
setDraftValue,
defaultValue,
setAmount,
customerName,
amount
} = useContext(Contx);
React.useEffect(() => {
setDraftValue(defaultValue);
});
// console.log("fffffff", customerName, amount);
return (
<div>
<input
type="text"
value={customerName}
name="customerName"
placeholder="Enter Customer name"
onChange={(e) => {
setCustomerName(e.target.value);
}}
/>
<input
type="number"
placeholder="Enter Amount"
value={amount}
onChange={(e) => setAmount(e.target.value)}
/>
</div>
);
}
DraftJS
import React, { useState } from "react";
import { Editor } from "react-draft-wysiwyg";
import {
EditorState,
convertToRaw,
ContentState,
convertFromHTML
} from "draft-js";
import "react-draft-wysiwyg/dist/react-draft-wysiwyg.css";
import draftToHtml from "draftjs-to-html";
import "./Data.css";
export default function NewDraft({
handleClose,
defaultValue,
defaultValueEmpty,
prsFunc
}) {
const initialState = defaultValueEmpty
? () => EditorState.createEmpty()
: EditorState.createWithContent(
ContentState.createFromBlockArray(convertFromHTML(defaultValue))
);
const [editorState, setEditorState] = useState(initialState);
const onChange = (value) => {
setEditorState(value);
};
const saveData = () => {
prsFunc(draftToHtml(convertToRaw(editorState.getCurrentContent())));
handleClose();
};
// console.log(draftToHtml(convertToRaw(editorState.getCurrentContent())));
return (
<div>
<div style={{ border: "2px solid", padding: "20px" }}>
<Editor
editorState={editorState}
toolbarClassName="toolbarClassName"
wrapperClassName="wrapperClassName"
editorClassName="editorClassName"
onEditorStateChange={(value) => onChange(value)}
/>
<button variant="secondary" onClick={saveData}>
Save
</button>
</div>
</div>
);
}
The problem you are facing is caused by this line in Data component. Every time the component is updated, the draftValue is set to the defaultValue.
React.useEffect(() => {
setDraftValue(defaultValue);
});

unmute react-player when fullscreen is toggled from outer component

I have a VideoItem- and a Player-component
In VideoList a button is clicked and is going to fullscreen mode (is working as expected)
I will unmute player when fullscreen is clicked.
How can I pass down a "mute" change from VideoList to Player? In my Player I also have a "Unmute" button (which is also working as expected:
This is what I have so far
VideoItem.jsx
import React, { useRef, useState, useEffect } from "react"
import { findDOMNode } from "react-dom"
import screenfull from "screenfull"
import VideoPlayer from "./VideoPlayer"
const VideoList = (videos) => {
const ref = useRef()
const toggleFullScreen = () => {
screenfull.request(findDOMNode(ref.current))
}
const unMute = () => {
console.log("Should pass Mute state to player", muted)
}
return (
<>
<VideoPlayer
ref={ref}
mute={muted}
videoURL={videoUrl}
/>
<a
href="#"
onClick={e => {
e.preventDefault()
unMute()
toggleFullScreen()
}}
>
Show Fullscreen
</a>
)
}
Player.jsx
import React, { forwardRef, useState, useEffect } from "react"
import ReactPlayer from "react-player"
const VideoPlayer = forwardRef((props, ref, mute) => {
let [muteState, setMuteState] = useState(true)
return (
<>
<i className={`fal fa-volume-${muteState ? "up" : "mute"}`}
onClick={() => {
setMuteState(!muteState)
}}
/>
<ReactPlayer
ref={ref}
muted={muteState}
loop={true}
playing={true}
url={props.videoURL}
/>
</>
)
}
Thank you!
When you attempt to set the state from the parent this usually is an indicator that you should move the state up and make the child controlled by the parent:
const VideoList = (videos) => {
const player = useRef();
const [muted, setMuted] = useState(true);
const [fullscreen, setFullscreen] = useState(false);
const handleToggleMute = () => setMuted(current => !current);
const handleFullscreen = event => {
event.preventDefault();
setMuted(false);
setFullscreen(true);
};
return (
<>
<VideoPlayer
ref={ref}
muted={muted}
fullscreen={fullscreen}
videoURL={videoUrl}
onToggleMute={handleToggleMute}
/>
<a href="#" onClick={handleFullscreen}>Show Fullscreen</a>
)
}
Also I would use useEffect together with another state fullscreen to avoid having to forward a ref of the video player.
const VideoPlayer = ({videoURL, muted, fullscreen, onToggleMute}) => {
const playerRef = useRef();
useEffect(() => {
if (fullscreen) {
const videoElem = playerRef.current.getInternalPlayer();
screenfull.request(videoElem);
}
}, [fullscreen]);
return (
<>
<i
className={`fal fa-volume-${muted ? "up" : "mute"}`}
onClick={onToggleMute}
/>
<ReactPlayer
ref={playerRef}
muted={muted}
loop={true}
playing={true}
url={videoURL}
/>
</>
)
}

Resources