I'm trying to build a modal in react. When it's opened, the background or the body color must change to a darker one and the modal should open, when closed, everything must go back to normal.
I tried to achieve this by this method:
const [modalState, setModalState] = useState(false);
const [modalBg, setModalbg] = useState(false);
function handleClick() {
setModalState(true);
setModalbg(true);
}
return (
{
!modalState ?
null
: <Modal modalState = {setModalState}/>
}
{
!modalBg ?
document.body.style.backgoundColor = "ffff"
: document.body.style.backgoundColor = 'rgba(39, 38, 38, 0.616)'
}
<button onClick= {handleClick}>Open</button>
)
The problem is, that instead of changing color of the body, it just renders text to the page like '#fff' or 'red'. I don't know why that happens, can anyone help?
Thanks!
Your way isn't really the "React" way of handling modals. First of all, I can't stress enough the use of portals when creating modals. Do look this up to really understand what portals are and how they help you.
Moving on, you can create a modal component as shown below and trigger this component however you want, using a state. Just make sure to pass the setter of that state to the component as a prop, in my case setShowModal.
Your modal's background is contained in the div my-modal-container and you can change that however you wish.
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
const MyModal = ({setShowModal}) => {
const handleClose = () => {
setShowModal(false)
};
return ReactDOM.createPortal(
<div className='my-modal-container'>
<div class='my-modal-body'>
//Place all you modal body here
</div>
</div>,
document.getElementById('portal')
);
};
export default MyModal;
If you choose to you use portals as above, make sure you add <div id="portal"></div> in your index.html file right below <div id="root"></div>.
You should not set the background colour of the body in the return function. That is meant to return the JSX.
Try using a useEffect hook something like:
useEffect(() => {
if (modalBg) {
document.body.style.backgoundColor = "rgba(39, 38, 38, 0.616)";
} else {
document.body.style.backgoundColor = "ffff";
}
}, [modalBg]);
Related
I have created a reproducible exam of my problem, I don't understand why after the setDefaultValue is called and the component is updated (you can see it's updated using the result of my console.log) If now I click on the reset button instead of the new defaultValue I see the old one.
Here is a link to the example showing this problem, I'll also paste the code here
https://codesandbox.io/s/wonderful-tree-wtsgb4?file=/src/App.js
import "./styles.css";
import {useState, useRef} from 'react';
import TextBox from './TextBox';
export default function App() {
const textboxAPI = useRef(null)
const [defaultValue ,setDefaultValue] = useState('First')
return (
<div className="App">
<div style={{fontWeight: 'bold'}}>To reproduce please first click on the default value button then on the reset button</div>
<TextBox getAPI={(api) => textboxAPI.current = api} defaultValue={defaultValue}/>
<button onClick={() => setDefaultValue('second')}>1- Click me to change default value to "second"</button>
<button onClick={() => textboxAPI.current.reset()}>2- Click me to call reset inside Textbox</button>
</div>
);
}
import {useEffect, useState} from 'react';
const TextBox = ({defaultValue, getAPI}) => {
const [value, setValue] = useState(defaultValue || '')
useEffect(() => {
if (getAPI) {
getAPI({
reset: reset,
})
}
}, [])
const reset = () => {
console.log('TextBox Reset DefaultValue', defaultValue)
setValue(defaultValue)
}
console.log('TextBox DefaultValue', defaultValue)
return <div>{value}</div>
}
export default TextBox;
To reproduce the problem:
1- Click on the first button to set a new defaultValue, see the console.log, you can see the defaultValue has changed inside the TextBox Component
2- Click the reset button, it calls the reset function inside TextBox but the default value logged there has the previous value!
Here you save in textboxAPI.current function reset but just one time after first render of TextBox component. Function reset has a defaultValue in a closure and its value is 'First' during first render. So each next time you call textboxAPI.current.reset(), you call the reset function with defaultValue==='First' in its closure.
But you parent component controls child state and React does not recommend to manage your logic like that.
[UPDATED]
That will fix your issue, but I don not recommend to organize a state logic like that:
const TextBox = ({defaultValue, getAPI}) => {
const [value, setValue] = useState(defaultValue || '')
const reset = () => {
console.log('TextBox Reset DefaultValue', defaultValue)
setValue(defaultValue)
}
if (getAPI) {
getAPI({
reset: reset,
})
}
console.log('TextBox DefaultValue', defaultValue)
return <div>{value}</div>
}
export default TextBox;
Based on what I learned from the comments I tried using Hooks but There were too many changes needed especially I had some issues with React.lazy so I tried to look for more solutions until I found that using a combination of forwardRef and useImperativeHandle I can export my reset function without changing my current structure and it works as it should, I thought that I should share my solution for anyone else who might be looking for an alternative solution to Hooks.
I installed react-mailchimp-subscribe and I want to change text button because my website is not written in English.
i putted this component into div className="pokemon" to have access to him like .pokemon > div > button and could have change styles. Now I want to change text.
I try to acces to him by using
useEffect(() = > {
document.addEventListener("load", function(){
(".pokemon>div>button").innerHtml("Wyślij")
}); }, [])
but I guess in my function is too many errors that it actually work.
You can update the button label using the state. Please refer the below code
import React, { useEffect, useState } from 'react'
export default function App() {
const [buttonLabel, setButtonLabel ] = useState("I'm Button");
useEffect(() => {
setButtonLabel("Wyślij")
}, [])
return (
<div className="pokemon">
<div>
<button> {buttonLabel}</button>
</div>
</div>
)
}
Here is the link to codesandbox environment
If you want to do it using dom, you can use the below code.
You have some minor syntax mistake.
useEffect(() = > {
document.addEventListener("load", function(){
document.querySelector(".pokemon").querySelector("div").querySelector("button").innerHTML = "Wyślij";
}); }, [])
I have a bottomTabNavigator which has two stacknavigators. Each stacknavigator has their own respective screens within them. Whenever I use something like
navigator.navigate("Stackname" {screen:"screenname", randomProp: "seomthing")
the params are sent to the stacknavigator, and not the screen itself. I kinda got past the issue by passing in
initialParams=route.params
within the stacknavigators, but they won't refresh when I call the first block of code for a second time.
Any ideas?
Instead of:
navigator.navigate("StackName" {screen:"screenName", paramPropKey: "paramPropValue");
Use this:
navigator.navigate("screenName", {'paramPropKey': 'paramPropValue'});
In screenName:
export default ({route}) => {
useEffect(() => {
// do something
}, [route]);
};
That is because the screen is already mounted & initial params won't update. What you can do, though, is create a wrapper component enhanced with 'withNavigationFocus' that 'react-native-navigation' offers.
https://reactnavigation.org/docs/1.x/with-navigation-focus/
ComponentWithFocus
import React, {Component, useState} from 'react';
import { withNavigationFocus } from 'react-navigation';
const ComponentWithFocus = (props) => {
const {isFocused, onFocus, onBlur} = props;
const [focused, setFocused] = useState(false);
if(isFocused !== focused) {
if(isFocused) {
typeof onFocus === 'function' ? onFocus() : null;
setFocused(true)
} else {
typeof onBlur === 'function' ? onBlur() : null;
setFocused(false)
}
}
return (
props.children
)
}
export default withNavigationFocus(ComponentWithFocus);
And use it in your screen like this:
...
onFocus = () => {
//your param fetch here and data get/set
this.props.navigation.getParam('param')
//get
//set
}
...
render() {
<ComponentWithFocus onFocus={this.onFocus}>
/// Your regular view JSX
</ComponentWithFocus>
}
Note: If params aren't updated still, than you should reconsider your navigating approach. For example, there is no need to navigate from your tabBar like this:
navigator.navigate("Stackname" {screen:"screenname", randomProp: "seomthing")
You could instead do the following:
navigator.navigate("screenName", {'paramPropKey': 'paramPropValue'})
This will work because '.navigate' function finds the first available screen that matches the name and if it isn't already mounted it mounts it onto the stack (firing componentDidMount method). If the screen already exists, it just navigates to it, ignoring 'componentDidMount' but passing the 'isFocused' prop which, luckily, we hooked on to in our 'ComponentWithFocus'.
Hope this helps.
function HomeScreenComponent( {navigation} ) {
React.useEffect(() => {
navigation.addListener('focus', () => {
console.log("reloaded");
});
}, [navigation]);
export default HomeScreenComponent;
This will also listen to the focusing and execute the useEffect function when the screen navigates.
Where's the right place to put code that interacts with the DOM in a gatsby site? I want to toggle the visibility of some components by adding/removing a class when another element is clicked.
The gatsby-browser.js file seems like it should contain this code but the API doesn't seem to call any of the functions after the DOM has loaded.
Similarly, using Helmet calls it too soon. Using window.onload never seems to trigger at all regardless of where it's included.
window.onload = function () {
// add event listener to the toggle control
}
Is there an event I can use to run my code when the DOM is ready?
Do you really need to wait for the DOM to be ready? When working in react you need to change the way you think about these things. For example you could add an on click that changes state and then reflect the state change in your classname prop.
Code Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the initial state
return (
<div>
<div className={visible ? "visible-class" : "hidden-class"}>
My content
</div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
</div>
)
}
export default MyApp
Or you could take it a step further and not even render that content to the DOM until you want to.
Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the inital state
return (
<div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
{visible && <div>My content here</div>}
</div>
)
}
export default MyApp
You can use the React cyclelife with componentDidMount().
This need to update your component like that :
import React from 'react'
class YourComponent extends React.Component {
componentDidMount() {
// Your Javascript function here
}
render() {
return(
<div className="YourComponentHere"></div>
)
}
}
export default YourComponent
Hope that help you!
If your component is a functional component, try using React Hook useEffect, which will guarantee the execution after the component is rendered.
import React, { useEffect } from 'react'
const MyComponent = () => {
useEffect(() => {
console.log("Document loaded");
});
return (
<main>
<text>Pretty much the component's body code around here</text>
</main>
)
}
export default MyComponent
ImagesUpload.jsx --> the Presentational component
deleteImageWarning.jsx --> the Notifications component
index.js --> where I exported the deleteImageWarning function
The goal
I want to include a notification or popup in my React app that alerts the user and gives them the choice to either cancel or confirm an action, in this case, deleting an image attached to a job sheet. This notification should be triggered when the user clicks the Delete button located next to the image that has been attached to the page.
Where to look for the issue
What I wrote (please have a look below) is not working whatsoever. I feel there is something wrong with the validateBeforeDelete function; I just wanted to have something that returns the notification function with the right values in the DOM. In addition, I am missing what to write in the Content section in the deleteImageWarning component.
Brief overview
To give you an idea, the button's delete functionality was working perfectly fine prior to working on the notification. There is a container for the ImagesUpload file, therefore, we could state that the ImagesUpload.jsx file is the Presentational Component and there is a ImagesUploadContainer.jsx file that acts as the Container Component for the Presentational Component.
The issue
The problem is that I don't know how to pass the delete function that I declared in the ImagesUpload.jsx file to the deleteImageWarning.jsx component. And that's surely what I am missing in the Content constant of my deleteImageWarning component. Does it have anything to do with the constants declared in my render() function?
ImagesUpload.jsx
//importing the deleteImageWarning function
import {
deleteImageWarning,
} from '../common/notifications';
//this is the function that deletes the image with the required values
async handleDeleteImage (jobsheetId, imageId) {
this.props.deleteImage({jobsheetId: jobsheetId, imageId: imageId});
}
//this is a validate function that is supposed to trigger the deleteImageWarning function
validateBeforeDelete = (jobsheetId, imageId) => {
return deleteImageWarning(this.notificationDOMRef.current, () => this.handleDeleteImage(jobsheetId, imageId));
}
render() {
const { ... } = this.props;
const { ... } = this.state;
return (
//TO BE AWARE! the following delete button with an onClick function has been written using React final form's syntax
...
<StyledSectionColRight>
<Button red type="button" onClick={() => this.validateBeforeDelete(id, image.id)}>Delete</Button>
</StyledSectionColRight>
...
);
}
export default ImagesUpload;
index.js
(Nothing really important, just in case someone thinks the error is due to not exporting deleteImageWarning)
//deleteImageWarning included in the index.js file
export { default as deleteImageWarning } from './deleteImageWarning';
deleteImageWarning.jsx
import React from 'react';
import styled from 'styled-components';
import { Button, ButtonInverted } from '../button';
const StyledNotification = styled.div`
background: var(--white);
padding: var(--spacer-m);
`;
const StyledMessage = styled.p`
font-size: var(--font-s);
line-height: var(--lineHeight-s);
margin: 0 0 var(--spacer-l) 0;
`;
const Content = ({ ????? }) => (
<StyledNotification>
<StyledMessage>
Are you sure you want to delete this image? This process cannot be undone.
</StyledMessage>
<Button type="button" red onClick={?????}>Confirm</Button>
<ButtonInverted type="button">Cancel</ButtonInverted>
</StyledNotification>
);
const deleteImageWarning = (node, ?????) => {
node.addNotification({
content: <Content ?????={?????} />,
type: "info",
title: "",
message: "",
insert: "top",
container: "top-center",
animationIn: ["animated", "fadeIn"],
animationOut: ["animated", "fadeOut"],
dismissable: { click: true },
width: 400
});
}
export default deleteImageWarning;
To make it super obvious, I have added a few question marks in the code to highlight where I don't know what to write.
I think your way of thinking is somewhat (not to be harsh) wrong, please correct me if I am missunderstanding. Where should the Content be rendered? I don't see a place where it would be.
What I would do is make a new Component (instead of a function) and render it only when a flag in the state (e.g. state = { ... , isdeleting = true }) turns true. You might also store the information of the image to be deletet in the state and pass it down to the Component:
//[prev. Code in ImagesUpload.jsx]
{ this.state.isdeleting ? <DeletImgWaring handleDeleteImage = {this.handleDeleteImage} imgInformation={//e.g. this.state.imgToBeDelInfo}
In this way you think more in component-based. And you can reuse the DeletImgWaring-Component somewhere else.
Does this help?
Regards