React Modal ref Undefined! Can't Add Custom Attribute - javascript

I have a simple modal:
renderModalForm() {
return (
<Modal
closeTimeoutMS={150}
show={this.state.isModalOpen}
onHide={this.isModalOpen.bind(this)}
>
<Modal.Body>
<div className="close-button-modal">
<i className="fa fa-times fa-2x pull-right" onClick={this.onButtonClick.bind(this)}></i>
<div className="clearfix"></div>
</div>
<div ref="test" className="testclassname">
</div>
</Modal.Body>
</Modal>
);
}
My sole objective is to inject a custom attribute (which unfortunately cannot start with data- or aria- since it's defined by third party) to the div referenced by ref="test"
When I attempt to inject the custom attribute:
someButtonClicked() {
setTimeout(() => {
this.setState({
isModalOpen: true
})
}, 100);
var element = ReactDOM.findDOMNode(this.refs.test);
element.setAttribute('doku-div', 'form-payment');
}
Here element is always undefined, so setAttribute failed; if I go inspect the element, ref="test" does not exist at the <div> ! Can someone help me as to why this ref is missing inside modal?

The correct way to implement is use Callback hook. Whenever the component will render you will have the ref element. useCallback will also help stop unnecessary renders hence better performance.
const Parent = () => {
const [isVisible, setIsVisible] = useState(false);
return (
<>
<button onClick={() => setIsVisible(!isVisible)}>Show/Hide Popup</button>
<Child isVisible={isVisible} />
</>
);
};
const Child = () => {
const customRef = useCallback((ref) => {
// ref element accessible here
}, []);
return (
<Modal show={isVisible}>
<Modal.Body>
<input ref={customRef} type="text" />
</Modal.Body>
</Modal>
);
};

Try moving your code to ComponentDidMount or ComponentDidUpdate method. Refs shouldn't be undefined there.
You can also use a callback to store a reference to the DOM node:
<Modal.Body>
<div className="close-button-modal">
<i className="fa fa-times fa-2x pull-right" onClick={this.onButtonClick.bind(this)}></i>
<div className="clearfix"></div>
</div>
<div ref="{(testNode) => { this.testNode = testNode; }}" className="testclassname">
</div>
</Modal.Body>
And then use that reference instead of using ReactDOM:
someButtonClicked() {
setTimeout(() => {
this.setState({
isModalOpen: true
})
}, 100);
var element = this.testNode;
this.testNode.setAttribute('doku-div', 'form-payment');
}

useRef hook will not work in modals as the component will mount but the jsx will not render until you make show prop to true. useRef is asynchronous in nature thats why at the time of declaration it sets current to null but after you assign it to any element ref got value of it. But in case of modals the case is different. Here the elements are not registered instantly but after modal show prop is set to true
To solve this make the modal's show prop always to true and make whole component to show/hide dynamically
similar query

Related

How can I make a component render onClick in a React functional component?

I'm a bit surprised I'm having trouble finding this online, but I can't seem to find an example of how to do this in a React functional component. I have a React component that I would like to render when I click a button. Right now the function fires and I can see my console.log firing, however the component isn't rendering. My first guess was that it won't render because React doesn't know to update the view, however I added boolean via useState and it still won't render. What am I doing wrong?
Below is the relevant code. How can I get the component in addSection to render?
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
return additionalSection && (
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
span className="button--small">{form.button}</span>
</LinkButton>
</FormAdd>
);
You should change your state (or a prop in your useEffect dependency array in case you had one) in order to force a rerender. In this case:
setAdditionalSection(prevState=>!prevState);
A state change like the one you are calling, will trigger a re-render.
But all html to be rendered must be included in the functional components return statement.
The elements you want to render can be conditionally rendered like this:
const FormGroup = ({index}) => {
const [additionalSection, setAdditionalSection] = useState(false);
const addSection = form => {
setAdditionalSection(true);
console.log('form', form);
};
...
return (
...
<FormAdd>
<LinkButton
type="button"
onClick={() => addSection(form)}
>
<span className="button--small">{form.button}</span>
</LinkButton>
{additionalSection &&
<div key={form.prop}>
<p>This should render</p>
<AdditiveSection
form={form}
register={register}
errors={errors}
/>
</div>
}
</FormAdd>
);

Remove dynamic rendered element from dom in ReactJS

Currently I've got a react component that looks like this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
return (
<div key={i} className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div>
);
});
};
Based on the cards array, it renders something like this:
Rendered Component
Whenever I click the trash button, I make a request to my backend, edit the list on my database and rerender the component based on the now updated "cards". The problem is that this takes sometime to happen, so i wanted a way to remove it from the dom instantly while my backend does it's job.
somehting like
{show ? renderCompoennt : null}
I've tried using vanilla javascript to grab the parent from the trash can, which would be the card i want to remove, but the results are unpredictable and it's quite slow as well.
My latest try was this:
const GeraCard = (cards, cart = false) => {
return cards.map((v, i) => {
const [show, setShow] = useState(true);
return (
<div key={i}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
});
};
but react won't let me do this. Even tho its fast, everytime one item gets deleted, react complains that "less hooks were rendered" and crashes the app.
You are attempting to do some Optimistic UI, in which you assume that your action will succeed, and reflect the expected/assumed state instantly, before the request to the backend completes. This would be in lieu of showing some progress/busy indicator, like a spinner, until the action completes with the server.
The first problem and immediate problem in your code-- it violates the rules of hooks, which state that hooks may only be used at the top-level (never inside loops, conditionals, etc).
The second problem is that you are leveraging vanilla JS to manipulate the DOM directly; this generally an antipattern in MV* frameworks, and very much so here. Instead, I would suggest doing managing it in your data model; something like this:
Rewrite your .map handler to return null if the card has a deleted property.
When the user clicks the trash button, do two things:
Make the request to the backend to delete it
Use a setState to add a deleted: true property to the clicked card
Now you will get a rerender that will omit the deleted card, and also make the request to the backend, all inside the React data model. Make sure that you handle complexity for:
How to handle the response
How to handle an error if the deletion fails at the backend
How to manage if a user quickly clicks many cards for deletion before any of the requests can complete.
The problem is that in the first render you have {cards.length} calls to hook "useState" within GeraCard, but after deletion of one card, you will have {cards.length-1} calls to hook "useState". As the React docs state:
Don’t call Hooks inside loops, conditions, or nested functions.
Instead, always use Hooks at the top level of your React function. By
following this rule, you ensure that Hooks are called in the same
order each time a component renders. That’s what allows React to
correctly preserve the state of Hooks between multiple useState and
useEffect calls.
You should extract the content of map callback into separate a component.
const GeraCards = (cards, cart = false) => {
return cards.map((v, i) =>
<GeraCard card={v} index={i} cart={cart} />
);
};
const GeraCard = ({ card, index, cart }) => {
const [show, setShow] = useState(true);
const v = card;
return (
<div key={index}>
{show ?
<div className={styles.card}>
<div onClick={() => urlRender(v.url)} className={styles.cardContent}>
<div>
<span className={styles.cardTitulo}>{v.Nome}</span>
</div>
<div>
<span className={styles.cardData}>{v.Data}</span>
<span className={styles.cardAtivos}>{v.Ativos} ativo(s)</span>
</div>
{cart ? <div>R$ {FormatCapital(v.Capital)}</div> : null}
</div>
<span className={styles.trash}>
<FontAwesomeIcon
icon={faTrash}
color={"#3c3c3c77"}
onClick={(e) => {
setShow(false);
e.persist()
TrashHandler(v.Nome, e)
}}
/>
</span>
</div> :
null
}
</div>
);
}

ReactJS: Hiding a text produces an error: Maximum update depth exceeded

I just cant hide my text (Header) using a button in a class form. I try this code below:
constructor(props){
super(props)
this.state = {
showHeader: true,
}
}
And I render the state above using setState:
render () {
return (
<div>
{this.state.showHeader && <Header /> }
<button onClick={ this.setState({showHeader: false})} >Hide</button>
</div>
I know this is a stupid question but I cant help myself because Im a totally beginner. But I did this right using function and I just want try to convert it using a class. This is what I did using function:
const [show, setShow] = React.useState(true);
const hideHeader = () => {
setShow(!show)
}
And return this:
return (
<div>
{show && <Header />}
<button onClick={hideHeader}>Hide Header</button>
</div>
)
Right now you're calling setState() in your render function. That's going to cause problems because setState causes your render method to be called, and if your render method calls setState directly, you get caught in a loop.
What you need to do is call it in an event handler instead:
// bad
onClick={this.setState({showHeader: false})}
// good
onClick={() => this.setState({showHeader: false})}
So your button should look like this:
<button onClick={() => this.setState({showHeader: false})} >Hide</button>
From the docs:
The render() function should be pure, meaning that it does not modify component state, it returns the same result each time it’s invoked, and it does not directly interact with the browser.

Cannot update during an existing state transition in stateless component

I have the following warning :
Warning: setState(...): Cannot update during an existing state transition (such as within render or another component's constructor).
with React-redux-router that I understand, but do not know how to fix.
This is the component that is generating the warning.
const Lobby = props => {
console.log("props", props)
if (!props.currentGame)
return (
<div>
<input type="text" ref={input => (roomName = input)} />
<button
className="button"
onClick={() => {
props.createRoom(roomName.value)
}}
>
Create a room
</button>
</div>
)
else
return (
<div>
{props.history.push(`/${props.currentGame}[${props.username}]`)}
</div>
)
}
export default Lobby
What I'm doing here is that my component receives the currentGame property from the Redux store. This property is initialized as null.
When the user creates a game, I want to redirect him on a new URL generated by the server that I assign inside the property currentGame with a socket.io action event that is already listening when the container of the component Lobby is initialized.
However, since the currentGame property changes, the component is re-rendered, and therefore the line
{props.history.push(`/${props.currentGame}[${props.username}]`)}
generates a warning since the property currentGame now has a value, and the history property should not get modified during the re-render.
Any idea on how to fix it ?
Thanks!
You should not write props.history.push in render, instead use Redirect
const Lobby = props => {
console.log("props", props)
if (!props.currentGame)
return (
<div>
<input type="text" ref={input => (roomName = input)} />
<button
className="button"
onClick={() => {
props.createRoom(roomName.value)
}}
>
Create a room
</button>
</div>
)
else
return (
<div>
<Redirect to={`/${props.currentGame}[${props.username}]`} />
</div>
)
}
Do one thing, instead of writing the condition and pushing with history.push(), just put the code inside componentDidMount() if you are trying to do in the beginning.
componentDidMount(){
if(condition){
history.push('/my-url');
}
}

How do I dynamically change the content of a React Bootstrap modal?

I'm trying to change the content of the modal after it has mounted, but I'm not able to find the correct nodes to change. I've attached refs to the nodes I'm interested in and try to alter them in componentDidMount(). But the nodes are not found -- comes up as null.
var Modal = ReactBootstrap.Modal;
const MyModal = React.createClass({
getInitialState() {
return { showModal: false };
},
close() {
this.setState({ showModal: false });
},
open() {
this.setState({ showModal: true });
},
componentDidMount() {
var theNode = ReactDOM.findDOMNode(this.refs.bigPic);
var theOtherNode = ReactDOM.findDOMNode(this.refs.bigPicInfo);
theNode.src = 'http://big-pic.png';
theOtherNode.innerHTML = "<strong> Something here</strong>";
},
render() {
return (
<div>
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title></Modal.Title>
</Modal.Header>
<Modal.Body>
<div><img ref="bigPic" src="" /></div>
</Modal.Body>
<Modal.Footer>
<p ref="bigPicInfo"></p>
</Modal.Footer>
</Modal>
</div>
);
}
});
ReactDOM.render(<MyModal/>, document.getElementById("my-modal"));
Dynamic content in React is driven by component state, the same way you're using this.state.showModal to dynamically make the modal appear or not. Anything that can possibly change should have a default setting in getInitialState, then call this.setState() with your new values.. this will trigger your component to re-render.
const MyModal = React.createClass({
getInitialState() {
return {
showModal: false,
bigPicSrc: '',
infoContent: ''
}
},
...
componentDidMount() {
this.setState({
bigPicSrc: 'http://big-pic.png'
infoContent: <strong>Something here</strong> // not a string, but a component
})
},
render() {
return (
<Modal show={this.state.showModal} onHide={this.close}>
<Modal.Header closeButton>
<Modal.Title></Modal.Title>
</Modal.Header>
<Modal.Body>
<div><img ref="bigPic" src={this.state.bigPicSrc} /></div>
</Modal.Body>
<Modal.Footer>
<p ref="bigPicInfo">{this.state.infoContent}</p>
</Modal.Footer>
</Modal>
)
}
})
I use node and react 16, before I was learn little more of Bootstrap, and now collecting my knowledge about bout react and bootstrap. On next away I make modal: First I am put CDN links with Bootstrap css and js, and jquery from index.html from public folder. Next make folder for components from SRC folder of ny app. Next step I put bootstrap code from new file example modal.js and change bootstrap class from clssName in React. And modal worked Klick on this text for show modal . And think for change content of modal must use data some data of Json file there You must connect id field of Json data and ref or id tigger event with you calling this modal. Ever different event calling Id field from data.json. I thing for that is best use switch case for easy choose.

Categories