different behavior with componentDidMount than useEffect when using jquery emoji plugin - javascript

I’m stuck using a jquery emoji plugin on one of my components until I finish with a custom plugin I’m building.
For some reason, when I call the emoji plugin inside of componentDidMount, everything works except the ability to utilize a custom button to show the emoji modal. When I use a custom button, the emoji plugin doesn’t attach the event to the button.
What’s crazy is that I can use the same exact code in useEffect, and it attaches the event listener to the custom button just fine.
I verified that the event listener is not attached by looking in the web console at events attached to the element after the page loaded.
You can easily reproduce this problem by placing this component somewhere in an app (and importing jquery with the emoji-area plugin):
import React, {useEffect} from 'react';
export default function CommentInput(props) {
useEffect(() => {
const id = props.blurtId,
$wysiwyg = $('#' + id).emojiarea({
button: '#emoji-btn' + id
});
$.emojiarea.path = '/js/jquery/emojis/';
$.emojiarea.icons = {
':smile:' : 'smile.png',
':angry:' : 'angry.png',
':flushed:' : 'flushed.png',
':neckbeard:' : 'neckbeard.png',
':laughing:' : 'laughing.png'
};
}, []);
return (
<>
<textarea id={props.blurtId} className='blurt-comment-input' />
<i id={'emoji-btn' + props.blurtId} className='fa fa-smile emoji-btn' />
</>
)
}
Simply change this to a class component, and you’ll see that within componentDidMount, everything works except the custom button. Any idea what could cause this change in behavior??
Here is the react class component version:
import React, {Component} from 'react';
class CommentInput extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const id = this.props.blurtId,
$wysiwyg = $('#' + id).emojiarea({
button: '#emoji-btn' + id
});
$.emojiarea.path = '/js/jquery/emojis/';
$.emojiarea.icons = {
':smile:' : 'smile.png',
':angry:' : 'angry.png',
':flushed:' : 'flushed.png',
':neckbeard:' : 'neckbeard.png',
':laughing:' : 'laughing.png'
};
};
render() {
return (
<>
<textarea id={this.props.blurtId} className='blurt-comment-input' />
<i id={'emoji-btn' + this.props.blurtId} className='fa fa-smile emoji-btn' />
</>
)
}
}
export default CommentInput;

There is a difference between when componentDidMount and useEffect fires.
From the useEffect docs :
Unlike componentDidMount and componentDidUpdate, the function passed
to useEffect fires after layout and paint

The big difference between componentDidMount and useEffect is that useEffect is run after every render, not just the first one. Since your render outputs a new element on each render, after the first render the DOM element you attached the emoji thing to doesn't exist anymore, and a new one with the same ID does.
Options:
Use componentDidUpdate to handle the subsequent renders. (You'll still need componentDidMount for the first one.)
Use a callback ref.

Related

Reactjs creates infinite loop using setTimeout

Here is my App.js that contains beside Header and the MainView
an AlertDialog. Alert dialog has two state controlled props:
msg={alertMsg} toggleClass={alertToggleClass}
The task of AlertDialog is to show if the className is "alertDlg" and disappear if it's "alertDlg alertDlgClosed". Testing it via the inspector manually (changing className) shows, that it works fine.
Therefore alertToggleClass is set to "alertDlg alertDlgClosed" when initializing, so that the alert dialog is hided by default.
Within the MainView Component (before render())
sendGlobalAlert("Test Alert Msg") gets called which is simply a callback to the showAlert(msg) method in App.js.
Now here goes the tricky part: calling setAlertToggleClass("alertDlg"); in showAlert(msg)-method shows the custom alert dialog as expected. However trying to disable it by calling setAlertToggleClass("alertDlg alertDlgClosed"); within the setTimeout creates an infinite loop to showAlert(msg)-method.
As far as I can see, there is no recursivity is in setTimeout(...).
I can't explain this behavior and would appreciate any helpful hints.
import './App.css';
import AlertDialog from './components/general/alert-dialog/AlertDialog';
import { Header } from './components/general/Header';
import MainView from './components/main/MainView';
import { useState } from "react";
function App() {
const [alertMsg,setAlertMsg] = useState("");
const [alertToggleClass,setAlertToggleClass] = useState("alertDlg alertDlgClosed");
function showAlert(msg){
console.log("Showing alert dialog");
setAlertMsg(msg); // set message
setAlertToggleClass("alertDlg"); // show alert dialog
setTimeout(function() {
if(alertToggleClass === "alertDlg" ){
setAlertToggleClass("alertDlg alertDlgClosed");
console.log("hide alert");
}
// setAlertToggleClass("alertDlg test");
},3500);
}
return (
<div className="container">
<Header/>
<MainView sendGlobalAlert={showAlert}/>
<AlertDialog msg={alertMsg} toggleClass={alertToggleClass} />
</div>
);
}
export default App;
The sendGlobalAlert("Test Alert Msg"); within the MainView was the issue here.
After this call, which is basically a callback to App.js's showAlert(msg) method, the props stored in state, that is used by AlertDialog, were changed. Due to an update to these props, AlertDialog rerendered and that again caused a rerender in App.js, which means that it elements had to rerender too. As it was including MainView, the rerender of App.js meant a rerender of MainView, which calls the sendGlobalAlert("Test Alert Msg"); and started the recursiveness.
This is how the MainView looks like:
const MainView = ({sendGlobalAlert}) => {
sendGlobalAlert("Test Alert Msg");
return (
<div className="main-view" >
<BillView/>
<InteractionView sendGlobalAlert={sendGlobalAlert}/>
</div>
)
}

React - call function in child component if parent state changes

I've got an invisible fullscreen event processing component that catches and processes all mouse events. On certain events I want to call a particular function in a child component. I created a state variable in the parent component. I am listening with use effect in the child component if there are some changes to the state variable in the parent component. However the effect hook is being called to often. Is there a better way of doing this? In particular I want to set drawing=true on mouse down and drawing=false on mouse up. If drawing transitions from true to false I want so save something in the database. Therefore it's important that the function in useState is being called only once.
I have these two possible solutions.
If you want useEffect not to be called in the first render, you have to use a custom hook, probably called useUpdateEffect. Here is a usage guide from react-use package
use useImperativeHandle to run a function in child from parent.
example:
import React, { forwardRef, useRef } from 'react';
export default function Parent() {
const ref = useRef<Ref>(null!);
const handleCallFunction = (): void => ref?.current?.functionInChild();
return (
<div>
<button onClick={handleCallFunction}>
call a function in child from parent
</button>
<Child ref={ref} />
</div>
);
}
type Ref = {
functionInChild: () => void;
};
type Props = {};
const Child = forwardRef<Ref, Props>((props, ref) => {
const functionInChild = () => {
console.log('called from parent');
};
React.useImperativeHandle(ref, () => ({ functionInChild }));
return <div>Child!</div>;
});
You have to put this in your code
useEffect(()=>{
/*Query logic*/
console.log('i fire once');},[your_variable]);
you_variable which you want to change when this variable changes his value

React conditional style on custom click

I am trying to change a class when onClick event is fired, my function is called and it seems to be working but the class just won't update un the render, this is my code:
import React from 'react';
const Something = props => {
let opened = false;
const toggle = () => {
opened = !opened;
};
return (
<div className={opened ? 'some-class opened' : 'some-class'}>
<div className="some-inner-class" onClick={toggle}>
...content
</div>
</div>
);
};
export default Something;
I just want to be able to change style on when click event is fired.
My suggestion to you will be to use react hooks in this scenario, by that I mean, something like this, I have updated your code with my suggestion;
Hope it helps;
import React, { useState } from 'react';
const Something = props => {
// ***because this doesn't exist in a function component and we'd want to persist this value of opened between function call, a react hook in a function component will be ideal in your case.
//let opened = false;
// *suggestion as per react documentation: useState hook lets us keep local state in a function component
const [opened, setOpened] = useState(false);
// ***you don't really need this part, see my approach in second div of return
// const toggle = () => {
// opened = !opened;
// };
return (
<div className={opened ? 'some-class opened' : 'some-class'}>
<div className="some-inner-class" onClick={() => setOpened(true)}>
<p>Click me</p>
</div>
</div>
);
};
export default Something;
You need to update state in React for a rerender to occur. You should be creating state in a constructor like:
constructor() {
super();
this.state = { toggled: false };
}
Then, in your toggle function you need to call
this.setState({toggled: !this.state.toggled});
Currently your component is a stateless functional component so you would either need to rewrite it as a class that extends React.component or you could use hooks, https://reactjs.org/docs/hooks-state.html. Since it looks like you're just starting to learn how to use React I would rewrite the component as a class first and then try to refactor it using hooks (the modern practice).

addEventListener in a React app isn't working

Some background:
I'm trying to consume a custom web component in a react app and trying to listen to events from the web component. I believe you can't just handle the events in the usual react way on custom web component.
i.e.
<custom-button onClick={this.clickHandler}></custom-button>.
I have tried this and it didn't work.
The problem:
So, I'm trying to listen to an event via addEventListener as you would do in vanilla js but the event handler is never getting called.
An example of this is below. I know <button/> isn't a custom web component but it demonstrates the problem:
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
this.buttonRef = React.createRef();
}
componentDidMount() {
// there are no errors attaching the handler
this.buttonRef.current.addEventListener("onClick", e => {
// this point is never reached
debugger;
console.log("onClick", e);
});
}
render() {
return <button ref={this.buttonRef}>click me</button>;
}
}
export default App;
Any help would be appreciated.
The event is called click, not onClick.
You don't need to use an eventListener to do it. In ReactJS, you can use a handler to the button, like this:
import React, { Component } from "react";
class App extends Component {
constructor(props) {
super(props);
}
btnHandler = e => {
console.log("onClick",e)
}
render() {
return <button onClick={this.btnHandler}> click me </button>
}
}
export default App;
Take a look at the ReactJS Handling Events docs.
You are mixing two functionalities here,
button will only react to onClick events and your ref will only react to 'click' if the ref is clicked which it isn't.
So the button currently has no functionality.
From react doc:
function CustomTextInput(props) {
// textInput must be declared here so the ref can refer to it
let textInput = React.createRef();
function handleClick() {
textInput.current.focus();
}
return (
<div>
<input
type="text"
ref={textInput} />
<input
type="button"
value="Focus the text input"
onClick={handleClick}
/>
</div>
);
}
This a solution to your problem. Hope is the right solution.
Also
<button onClick={this.onClick}>click me</button>
can be written as:
<button onClick={e => this.onClick({e, ...})}>click me</button>
to have access to custom props you want to pass along to your button Event Listener.
In this situation you Event Listener needs to look like:
onClick = (e, ...) => {
// this point is never reached
debugger;
console.log("onClick", e);
};
Please note the ... in there needs to be a key => value pair.
import React, { Component } from "react";
export default class App extends Component {
constructor(props) {
super(props);
}
onClick = e => {
// this point is never reached
debugger;
console.log("onClick", e);
};
render() {
return <button onClick={this.onClick}>click me</button>;
}
}
Also try not to use React References, unless you really need to. They are not intended to be used outside of React's core functionality.

sharing a common state among duplicate react component in React JS

I have one doubt regarding state of component. I have one tile component. On click of tile component I am toggling the details of tile component.
/* ---- Tile component ---- */
import React, { Component } from 'react';
import TileDetail from './TileDetail';
class Tile extends Component {
constructor(props) {
super(props);
this.state = {
isTileDetailOpened:false
};
this.showTileDetails=this.showTileDetails.bind(this);
}
showTileDetails(networkName){
this.setState({isTileDetailOpened:!this.state.isTileDetailOpened});
alert(networkName);
}
render() {
const isTileDetailOpened = this.state.isTileDetailOpened;
return (
<li className={ !this.state.isTileDetailOpened ? "tile-item" : "tile-item-with-detail" }>
<div onClick={(e) => this.showTileDetails(this.props.objTile.network_name)}>
{this.props.objTile.network_name}
</div>
{this.state.isTileDetailOpened ? <TileDetail /> : <div/>}
</li>
);
}
}
export default Tile;
/* ---- Tile Detail component ---- */
import React, { Component } from 'react';
class TileDetail extends Component {
render() {
return (
<div className="tile-detail">
<p>TileDetails</p>
</div>
);
}
}
export default TileDetail;
As you can see, I am toggling the tile detail on click of tile click and its working fine.
I am rendering collecting of tiles on my page. And it works fine if I click on individual tile. But What I want if I am seeing the detail of one tile other tile details should be always hidden.
Could you please guide me on this. Completely new on React JS
You'll have to make use of the parent component of the Tile to achieve this. In the parent component have a state like currentlyTileDetailOpened. In showTileDetails call a method via props to set the value of this state(currentlyTileDetailOpened) to the id of the current Tile. Pass this state to the Tile component as a prop. In render method of Tile, check the prop instead of state and render it like
{this.props.currentlyOpenedTile === tile.id ? <TileDetail /> : null}
I just did this with a pagination control unit I made by storing the page info in Redux, and then making a special option in the second paginator so that it just used the Redux data instead of "double performing" the same work (and thus also not going out of sync with the first paginator).
It's like this (you could do the same with setting the parent's state (ie: first common ancestor):
<Pagination
pageSize={25}
items={(!this.state.fuzzyMatches.length)
? this.props.businesses
: this.state.fuzzyMatches}
onChangePage={(pagerState) => this.handlePageChange(pagerState)}
activePage={this.props.activePage}
isEmpty={((this.state.search.length > 0) &&
(this.state.fuzzyMatches.length === 0))}
bottom // bottom = true
bottomData={this.props.pagination} // get data from here
/>
Now, inside the paginator, instead of the normal logic, it does this:
render() {
const pager = (this.props.bottom)
? this.props.bottomData
: this.state.pager
The bottom = true prop is actually just to handle a slight CSS difference, but you can see, the first paginator creates the details, and the second one simply uses those instead of duplicating. I have told the second component to skip all its internal work and just use the data that is passed in.

Categories

Resources